diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/ExecutorSession.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/ExecutorSession.cs
index 0ba4a904a77..c7b18dd6255 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/ExecutorSession.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/ExecutorSession.cs
@@ -267,6 +267,14 @@ public async Task WriteSchemaAsync(
Version,
context.RequestAborted);
+ public async Task WriteSemanticNonNullSchemaAsync(
+ HttpContext context)
+ => await _responseFormatter.FormatSemanticNonNullSchemaAsync(
+ context.Response,
+ Schema,
+ Version,
+ context.RequestAborted);
+
public RequestFlags CreateRequestFlags(AcceptMediaType[] acceptMediaTypes)
=> _responseFormatter.CreateRequestFlags(acceptMediaTypes);
}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Extensions/EndpointRouteBuilderExtensions.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Extensions/EndpointRouteBuilderExtensions.cs
index ab81e24d398..70465cc1edd 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Extensions/EndpointRouteBuilderExtensions.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Extensions/EndpointRouteBuilderExtensions.cs
@@ -26,6 +26,7 @@ public static class EndpointRouteBuilderExtensions
private const string GraphQLHttpPath = "/graphql";
private const string GraphQLWebSocketPath = "/graphql/ws";
private const string GraphQLSchemaPath = "/graphql/sdl";
+ private const string GraphQLSemanticNonNullSchemaPath = "/graphql/semantic-non-null-schema.graphql";
private const string GraphQLToolPath = "/graphql/ui";
private const string GraphQLPersistedOperationPath = "/graphql/persisted";
private const string GraphQLToolRelativeRequestPath = "..";
@@ -389,6 +390,88 @@ public static IEndpointConventionBuilder MapGraphQLSchema(
.WithDisplayName("Hot Chocolate GraphQL Schema Pipeline"));
}
+ ///
+ /// Adds a GraphQL semantic non-null schema SDL endpoint to the endpoint configurations.
+ /// The endpoint serves a schema document where non-null wrappers have been replaced with
+ /// the @semanticNonNull directive.
+ ///
+ ///
+ /// The .
+ ///
+ ///
+ /// The path to which the GraphQL semantic non-null schema endpoint shall be mapped.
+ ///
+ ///
+ /// The name of the schema that shall be used by this endpoint.
+ ///
+ ///
+ /// Returns the so that
+ /// configuration can be chained.
+ ///
+ ///
+ /// The is null.
+ ///
+ public static IEndpointConventionBuilder MapGraphQLSemanticNonNullSchema(
+ this IEndpointRouteBuilder endpointRouteBuilder,
+ [StringSyntax("Route")] string pattern = GraphQLSemanticNonNullSchemaPath,
+ string? schemaName = null)
+ => MapGraphQLSemanticNonNullSchema(endpointRouteBuilder, Parse(pattern), schemaName);
+
+ ///
+ /// Adds a GraphQL semantic non-null schema SDL endpoint to the endpoint configurations.
+ /// The endpoint serves a schema document where non-null wrappers have been replaced with
+ /// the @semanticNonNull directive.
+ ///
+ ///
+ /// The .
+ ///
+ ///
+ /// The path to which the GraphQL semantic non-null schema endpoint shall be mapped.
+ ///
+ ///
+ /// The name of the schema that shall be used by this endpoint.
+ ///
+ ///
+ /// Returns the so that
+ /// configuration can be chained.
+ ///
+ ///
+ /// The is null.
+ ///
+ public static IEndpointConventionBuilder MapGraphQLSemanticNonNullSchema(
+ this IEndpointRouteBuilder endpointRouteBuilder,
+ RoutePattern pattern,
+ string? schemaName = null)
+ {
+ ArgumentNullException.ThrowIfNull(endpointRouteBuilder);
+ ArgumentNullException.ThrowIfNull(pattern);
+
+ TryResolveSchemaName(endpointRouteBuilder.ServiceProvider, ref schemaName);
+
+ var requestPipeline = endpointRouteBuilder.CreateApplicationBuilder();
+ var schemaNameOrDefault = schemaName ?? ISchemaDefinition.DefaultName;
+
+ var services = endpointRouteBuilder.ServiceProvider;
+ var executorProvider = services.GetRequiredService();
+ var executorEvents = services.GetRequiredService();
+ var executor = new HttpRequestExecutorProxy(executorProvider, executorEvents, schemaNameOrDefault);
+ var serverOptions = services.GetServerOptions(schemaNameOrDefault);
+
+ requestPipeline
+ .Use(MiddlewareFactory.CreateCancellationMiddleware())
+ .Use(MiddlewareFactory.CreateHttpGetSemanticNonNullSchemaMiddleware(executor, serverOptions))
+ .Use(_ => context =>
+ {
+ context.Response.StatusCode = 404;
+ return Task.CompletedTask;
+ });
+
+ return new GraphQLEndpointConventionBuilder(
+ endpointRouteBuilder
+ .Map(pattern, requestPipeline.Build())
+ .WithDisplayName("Hot Chocolate GraphQL Semantic Non-Null Schema Pipeline"));
+ }
+
///
/// Adds a Nitro endpoint to the endpoint configurations.
///
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Formatters/DefaultHttpResponseFormatter.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Formatters/DefaultHttpResponseFormatter.cs
index 4360976be59..61f2d91abe7 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Formatters/DefaultHttpResponseFormatter.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Formatters/DefaultHttpResponseFormatter.cs
@@ -24,6 +24,7 @@ namespace HotChocolate.AspNetCore.Formatters;
public class DefaultHttpResponseFormatter : IHttpResponseFormatter
{
private readonly ConcurrentDictionary _schemaCache = new(StringComparer.Ordinal);
+ private readonly ConcurrentDictionary _semanticNonNullSchemaCache = new(StringComparer.Ordinal);
private readonly ITimeProvider _timeProvider;
private readonly FormatInfo _defaultFormat;
private readonly FormatInfo _graphqlResponseFormat;
@@ -344,6 +345,40 @@ CachedSchemaOutput Update(string _)
=> new(schema, version, _timeProvider.UtcNow);
}
+ public async ValueTask FormatSemanticNonNullSchemaAsync(
+ HttpResponse response,
+ ISchemaDefinition schema,
+ ulong version,
+ CancellationToken cancellationToken)
+ {
+ var output = _semanticNonNullSchemaCache.GetOrAdd(schema.Name, Update);
+
+ if (output.Version < version)
+ {
+ lock (_semanticNonNullSchemaCache)
+ {
+ if (!_semanticNonNullSchemaCache.TryGetValue(schema.Name, out output)
+ || output.Version < version)
+ {
+ _semanticNonNullSchemaCache[schema.Name] = output = Update(schema.Name);
+ }
+ }
+ }
+
+ var memory = output.AsMemory();
+ response.ContentType = ContentType.GraphQL;
+ response.Headers.SetContentDisposition(output.FileName);
+ response.Headers.ETag = output.ETag;
+ response.Headers.LastModified = output.LastModified;
+ response.Headers.CacheControl = "public, max-age=3600, must-revalidate";
+ response.Headers.ContentLength = memory.Length;
+ await response.Body.WriteAsync(memory, cancellationToken);
+ return;
+
+ CachedSemanticNonNullSchemaOutput Update(string _)
+ => new(schema, version, _timeProvider.UtcNow);
+ }
+
///
/// Determines which status code shall be returned for this result.
///
@@ -865,4 +900,43 @@ private static string GetSchemaFileName(ISchemaDefinition schema)
? "schema.graphql"
: schema.Name + ".schema.graphql";
}
+
+ private sealed class CachedSemanticNonNullSchemaOutput
+ {
+ private readonly byte[] _schema;
+
+ public CachedSemanticNonNullSchemaOutput(ISchemaDefinition schema, ulong version, DateTimeOffset lastModifiedTime)
+ {
+ var document = (HotChocolate.Language.DocumentNode)schema.ToSyntaxNode();
+ var rewritten = SemanticNonNullSchemaRewriter.Rewrite(document);
+ _schema = Encoding.UTF8.GetBytes(rewritten.ToString(indented: true));
+ FileName = GetSchemaFileName(schema);
+ ETag = CreateETag(_schema, version);
+ LastModified = lastModifiedTime.ToString("R");
+ Version = version;
+ }
+
+ public string FileName { get; }
+
+ public string ETag { get; }
+
+ public ulong Version { get; }
+
+ public string LastModified { get; }
+
+ public ReadOnlyMemory AsMemory() => _schema;
+
+ private static string CreateETag(byte[] schema, ulong version)
+ {
+ Span hashBytes = stackalloc byte[32];
+ SHA256.HashData(schema, hashBytes);
+ var hash = Convert.ToBase64String(hashBytes);
+ return $"\"{version}-{hash}\"";
+ }
+
+ private static string GetSchemaFileName(ISchemaDefinition schema)
+ => schema.Name.Equals(ISchemaDefinition.DefaultName, StringComparison.OrdinalIgnoreCase)
+ ? "schema.graphql"
+ : schema.Name + ".schema.graphql";
+ }
}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Formatters/IHttpResponseFormatter.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Formatters/IHttpResponseFormatter.cs
index 40f6305ea0b..4b5bc6e5558 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Formatters/IHttpResponseFormatter.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Formatters/IHttpResponseFormatter.cs
@@ -70,4 +70,29 @@ ValueTask FormatAsync(
ISchemaDefinition schema,
ulong version,
CancellationToken cancellationToken);
+
+ ///
+ /// Formats the given into a GraphQL schema SDL response that has
+ /// non-null wrappers replaced with the @semanticNonNull directive.
+ ///
+ ///
+ /// The HTTP response.
+ ///
+ ///
+ /// The GraphQL schema.
+ ///
+ ///
+ /// The schema version.
+ ///
+ ///
+ /// The request cancellation token.
+ ///
+ ///
+ /// A task that represents the asynchronous operation.
+ ///
+ ValueTask FormatSemanticNonNullSchemaAsync(
+ HttpResponse response,
+ ISchemaDefinition schema,
+ ulong version,
+ CancellationToken cancellationToken);
}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Formatters/SemanticNonNullSchemaRewriter.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Formatters/SemanticNonNullSchemaRewriter.cs
new file mode 100644
index 00000000000..6441d3c215e
--- /dev/null
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Formatters/SemanticNonNullSchemaRewriter.cs
@@ -0,0 +1,352 @@
+using HotChocolate.Language;
+using HotChocolate.Types;
+
+namespace HotChocolate.AspNetCore.Formatters;
+
+///
+/// Rewrites a GraphQL schema document by stripping non-null wrappers from
+/// applicable output fields and applying the @semanticNonNull directive to
+/// communicate the original nullability intent.
+///
+internal static class SemanticNonNullSchemaRewriter
+{
+ private const string MutationTypeName = "Mutation";
+ private const string PageInfoTypeName = "PageInfo";
+ private const string CollectionSegmentInfoTypeName = "CollectionSegmentInfo";
+ private const string NodeInterfaceName = "Node";
+ private const string IdFieldName = "id";
+
+ ///
+ /// Rewrites the supplied schema document and returns a new document that
+ /// uses the @semanticNonNull directive in place of non-null type wrappers
+ /// on object and interface fields.
+ ///
+ ///
+ /// The schema document to rewrite.
+ ///
+ ///
+ /// The rewritten schema document.
+ ///
+ public static DocumentNode Rewrite(DocumentNode schema)
+ {
+ var mutationTypeName = ResolveMutationTypeName(schema);
+ var definitions = new List(schema.Definitions.Count);
+ var anyRewritten = false;
+
+ foreach (var definition in schema.Definitions)
+ {
+ if (definition is ObjectTypeDefinitionNode objectType)
+ {
+ if (ShouldSkipObjectType(objectType, mutationTypeName))
+ {
+ definitions.Add(objectType);
+ continue;
+ }
+
+ var rewrittenObject = RewriteFields(objectType.Fields, out var rewritten);
+ if (rewritten)
+ {
+ anyRewritten = true;
+ definitions.Add(objectType.WithFields(rewrittenObject));
+ }
+ else
+ {
+ definitions.Add(objectType);
+ }
+ }
+ else if (definition is InterfaceTypeDefinitionNode interfaceType)
+ {
+ if (interfaceType.Name.Value == NodeInterfaceName)
+ {
+ definitions.Add(interfaceType);
+ continue;
+ }
+
+ var rewrittenInterface = RewriteFields(interfaceType.Fields, out var rewritten);
+ if (rewritten)
+ {
+ anyRewritten = true;
+ definitions.Add(interfaceType.WithFields(rewrittenInterface));
+ }
+ else
+ {
+ definitions.Add(interfaceType);
+ }
+ }
+ else
+ {
+ definitions.Add(definition);
+ }
+ }
+
+ if (!anyRewritten)
+ {
+ return schema;
+ }
+
+ if (!HasDirectiveDefinition(definitions, DirectiveNames.SemanticNonNull.Name))
+ {
+ var directive = CreateSemanticNonNullDirectiveDefinition();
+ var insertionIndex = FindDirectiveInsertionIndex(definitions, directive.Name.Value);
+ definitions.Insert(insertionIndex, directive);
+ }
+
+ return schema.WithDefinitions(definitions);
+ }
+
+ private static bool HasDirectiveDefinition(List definitions, string directiveName)
+ {
+ for (var i = 0; i < definitions.Count; i++)
+ {
+ if (definitions[i] is DirectiveDefinitionNode existing
+ && existing.Name.Value == directiveName)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private static int FindDirectiveInsertionIndex(List definitions, string directiveName)
+ {
+ var firstDirectiveIdx = -1;
+ var lastDirectiveIdx = -1;
+
+ for (var i = 0; i < definitions.Count; i++)
+ {
+ if (definitions[i] is DirectiveDefinitionNode)
+ {
+ if (firstDirectiveIdx < 0)
+ {
+ firstDirectiveIdx = i;
+ }
+ lastDirectiveIdx = i;
+ }
+ }
+
+ if (firstDirectiveIdx >= 0)
+ {
+ for (var i = firstDirectiveIdx; i <= lastDirectiveIdx; i++)
+ {
+ if (definitions[i] is DirectiveDefinitionNode existing
+ && string.CompareOrdinal(directiveName, existing.Name.Value) < 0)
+ {
+ return i;
+ }
+ }
+
+ return lastDirectiveIdx + 1;
+ }
+
+ var firstScalarIdx = -1;
+ var afterLastEnumIdx = -1;
+
+ for (var i = 0; i < definitions.Count; i++)
+ {
+ if (definitions[i] is EnumTypeDefinitionNode)
+ {
+ afterLastEnumIdx = i + 1;
+ }
+ else if (firstScalarIdx < 0 && definitions[i] is ScalarTypeDefinitionNode)
+ {
+ firstScalarIdx = i;
+ }
+ }
+
+ if (firstScalarIdx >= 0)
+ {
+ return firstScalarIdx;
+ }
+
+ if (afterLastEnumIdx >= 0)
+ {
+ return afterLastEnumIdx;
+ }
+
+ return definitions.Count;
+ }
+
+ private static string ResolveMutationTypeName(DocumentNode schema)
+ {
+ foreach (var definition in schema.Definitions)
+ {
+ if (definition is SchemaDefinitionNode schemaDefinition)
+ {
+ foreach (var operationType in schemaDefinition.OperationTypes)
+ {
+ if (operationType.Operation == OperationType.Mutation)
+ {
+ return operationType.Type.Name.Value;
+ }
+ }
+
+ return MutationTypeName;
+ }
+ }
+
+ return MutationTypeName;
+ }
+
+ private static bool ShouldSkipObjectType(ObjectTypeDefinitionNode objectType, string mutationTypeName)
+ {
+ var name = objectType.Name.Value;
+
+ if (name.StartsWith("__", StringComparison.Ordinal))
+ {
+ return true;
+ }
+
+ if (name == PageInfoTypeName || name == CollectionSegmentInfoTypeName)
+ {
+ return true;
+ }
+
+ if (name == mutationTypeName)
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ private static IReadOnlyList RewriteFields(
+ IReadOnlyList fields,
+ out bool rewritten)
+ {
+ rewritten = false;
+ var result = new List(fields.Count);
+
+ foreach (var field in fields)
+ {
+ var fieldName = field.Name.Value;
+
+ if (fieldName.StartsWith("__", StringComparison.Ordinal) || fieldName == IdFieldName)
+ {
+ result.Add(field);
+ continue;
+ }
+
+ var levels = GetSemanticNonNullLevels(field.Type);
+
+ if (levels.Count < 1)
+ {
+ result.Add(field);
+ continue;
+ }
+
+ rewritten = true;
+ var nullableType = ToNullableType(field.Type);
+ var newDirective = CreateSemanticNonNullDirective(levels);
+ var directives = new List(field.Directives.Count + 1);
+ var replaced = false;
+
+ foreach (var existing in field.Directives)
+ {
+ if (!replaced && existing.Name.Value == DirectiveNames.SemanticNonNull.Name)
+ {
+ directives.Add(newDirective);
+ replaced = true;
+ }
+ else
+ {
+ directives.Add(existing);
+ }
+ }
+
+ if (!replaced)
+ {
+ directives.Add(newDirective);
+ }
+
+ result.Add(field.WithType(nullableType).WithDirectives(directives));
+ }
+
+ return result;
+ }
+
+ private static List GetSemanticNonNullLevels(ITypeNode type)
+ {
+ var levels = new List();
+ var current = type;
+ var index = 0;
+
+ while (true)
+ {
+ if (current is ListTypeNode listType)
+ {
+ index++;
+ current = listType.Type;
+ }
+ else if (current is NonNullTypeNode nonNullType)
+ {
+ if (!levels.Contains(index))
+ {
+ levels.Add(index);
+ }
+ current = nonNullType.Type;
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ return levels;
+ }
+
+ private static ITypeNode ToNullableType(ITypeNode type)
+ {
+ if (type is ListTypeNode listType)
+ {
+ return new ListTypeNode(ToNullableType(listType.Type));
+ }
+
+ if (type is NonNullTypeNode nonNullType)
+ {
+ return ToNullableType(nonNullType.Type);
+ }
+
+ return type;
+ }
+
+ private static DirectiveNode CreateSemanticNonNullDirective(List levels)
+ {
+ if (levels.Count == 1 && levels[0] == 0)
+ {
+ return new DirectiveNode(DirectiveNames.SemanticNonNull.Name);
+ }
+
+ var items = new IValueNode[levels.Count];
+ for (var i = 0; i < levels.Count; i++)
+ {
+ items[i] = new IntValueNode(levels[i]);
+ }
+
+ return new DirectiveNode(
+ DirectiveNames.SemanticNonNull.Name,
+ new ArgumentNode(DirectiveNames.SemanticNonNull.Arguments.Levels, new ListValueNode(items)));
+ }
+
+ private static DirectiveDefinitionNode CreateSemanticNonNullDirectiveDefinition()
+ {
+ var levelsType = new ListTypeNode(new NonNullTypeNode(new NamedTypeNode("Int")));
+ var defaultValue = new ListValueNode(new IntValueNode(0));
+
+ var argument = new InputValueDefinitionNode(
+ location: null,
+ name: new NameNode(DirectiveNames.SemanticNonNull.Arguments.Levels),
+ description: null,
+ type: levelsType,
+ defaultValue: defaultValue,
+ directives: Array.Empty());
+
+ return new DirectiveDefinitionNode(
+ location: null,
+ name: new NameNode(DirectiveNames.SemanticNonNull.Name),
+ description: null,
+ isRepeatable: false,
+ arguments: new[] { argument },
+ locations: new[] { new NameNode("FIELD_DEFINITION") });
+ }
+}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/HttpGetSemanticNonNullSchemaMiddleware.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/HttpGetSemanticNonNullSchemaMiddleware.cs
new file mode 100644
index 00000000000..81d2d0716ee
--- /dev/null
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/HttpGetSemanticNonNullSchemaMiddleware.cs
@@ -0,0 +1,43 @@
+using HotChocolate.AspNetCore.Instrumentation;
+using Microsoft.AspNetCore.Http;
+using HttpRequestDelegate = Microsoft.AspNetCore.Http.RequestDelegate;
+
+namespace HotChocolate.AspNetCore;
+
+public sealed class HttpGetSemanticNonNullSchemaMiddleware : MiddlewareBase
+{
+ public HttpGetSemanticNonNullSchemaMiddleware(
+ HttpRequestDelegate next,
+ HttpRequestExecutorProxy executor,
+ GraphQLServerOptions baseOptions)
+ : base(next, executor, baseOptions)
+ {
+ }
+
+ public async Task InvokeAsync(HttpContext context)
+ {
+ if (HttpMethods.IsGet(context.Request.Method))
+ {
+ var session = await Executor.GetOrCreateSessionAsync(context.RequestAborted);
+ var options = GetOptions(context);
+
+ if (options.EnableSchemaRequests)
+ {
+ using (session.DiagnosticEvents.ExecuteHttpRequest(context, HttpRequestKind.HttpGetSemanticNonNullSchema))
+ {
+ if (!options.EnableSchemaFileSupport)
+ {
+ context.Response.StatusCode = 404;
+ return;
+ }
+
+ await session.WriteSemanticNonNullSchemaAsync(context);
+ }
+
+ return;
+ }
+ }
+
+ await NextAsync(context);
+ }
+}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Instrumentation/HttpRequestKind.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Instrumentation/HttpRequestKind.cs
index eb22d193d8e..3b8febc7f5a 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Instrumentation/HttpRequestKind.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Instrumentation/HttpRequestKind.cs
@@ -20,6 +20,11 @@ public enum HttpRequestKind
///
HttpGetSchema,
+ ///
+ /// HTTP GET semantic non-null SDL request.
+ ///
+ HttpGetSemanticNonNullSchema,
+
///
/// HTTP POST GraphQL MultiPart Request.
///
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/MiddlewareFactory.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/MiddlewareFactory.cs
index 7c23d6a3785..a9a13e5f795 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/MiddlewareFactory.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/MiddlewareFactory.cs
@@ -86,4 +86,15 @@ internal static Func CreateHttpGetSchemaMiddle
return context => middleware.InvokeAsync(context);
};
}
+
+ internal static Func CreateHttpGetSemanticNonNullSchemaMiddleware(
+ HttpRequestExecutorProxy executor,
+ GraphQLServerOptions serverOptions)
+ {
+ return next =>
+ {
+ var middleware = new HttpGetSemanticNonNullSchemaMiddleware(next, executor, serverOptions);
+ return context => middleware.InvokeAsync(context);
+ };
+ }
}
diff --git a/src/HotChocolate/AspNetCore/test/AspNetCore.Tests.Utilities/ServerTestBase.cs b/src/HotChocolate/AspNetCore/test/AspNetCore.Tests.Utilities/ServerTestBase.cs
index cfa19e39c59..245e951f92f 100644
--- a/src/HotChocolate/AspNetCore/test/AspNetCore.Tests.Utilities/ServerTestBase.cs
+++ b/src/HotChocolate/AspNetCore/test/AspNetCore.Tests.Utilities/ServerTestBase.cs
@@ -128,6 +128,7 @@ protected virtual TestServer CreateStarWarsServer(
var builder = endpoints.MapGraphQL(pattern);
configureConventions?.Invoke(builder);
+ endpoints.MapGraphQLSemanticNonNullSchema();
endpoints.MapGraphQL("/notnull", "notnull");
endpoints.MapGraphQL("/evict", "evict");
endpoints.MapGraphQL("/arguments", "arguments");
diff --git a/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/HttpGetSemanticNonNullSchemaMiddlewareTests.cs b/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/HttpGetSemanticNonNullSchemaMiddlewareTests.cs
new file mode 100644
index 00000000000..87598364708
--- /dev/null
+++ b/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/HttpGetSemanticNonNullSchemaMiddlewareTests.cs
@@ -0,0 +1,117 @@
+using System.Net;
+using HotChocolate.AspNetCore.Tests.Utilities;
+using HotChocolate.Execution;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+
+namespace HotChocolate.AspNetCore;
+
+public class HttpGetSemanticNonNullSchemaMiddlewareTests : ServerTestBase
+{
+ public HttpGetSemanticNonNullSchemaMiddlewareTests(TestServerFactory serverFactory)
+ : base(serverFactory)
+ {
+ }
+
+ [Fact]
+ public async Task Download_GraphQL_SemanticNonNull_Schema()
+ {
+ // arrange
+ var server = CreateStarWarsServer(
+ configureServices: sp =>
+ sp.AddGraphQLServer()
+ .ConfigureSchemaServices(s =>
+ s.RemoveAll()
+ .AddSingleton()));
+ var url = TestServerExtensions.CreateUrl("/graphql/semantic-non-null-schema.graphql");
+ var request = new HttpRequestMessage(HttpMethod.Get, url);
+
+ // act
+ var response = await server.CreateClient().SendAsync(request);
+
+ // assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ response.Headers.Remove("ETag");
+ response.Content.Headers.ContentLength = null;
+
+ response.MatchMarkdownSnapshot();
+ }
+
+ [Fact]
+ public async Task Download_GraphQL_SemanticNonNull_Schema_Not_Allowed_When_FileSupport_Disabled()
+ {
+ // arrange
+ var server = CreateStarWarsServer(
+ configureServices: s => s
+ .AddGraphQL()
+ .ModifyServerOptions(o => o.EnableSchemaFileSupport = false));
+ var url = TestServerExtensions.CreateUrl("/graphql/semantic-non-null-schema.graphql");
+ var request = new HttpRequestMessage(HttpMethod.Get, url);
+
+ // act
+ var response = await server.CreateClient().SendAsync(request);
+
+ // assert
+ Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
+ }
+
+ [Fact]
+ public async Task Download_GraphQL_SemanticNonNull_Schema_Disabled_When_SchemaRequests_Disabled()
+ {
+ // arrange
+ var server = CreateStarWarsServer(
+ configureServices: s => s
+ .AddGraphQL()
+ .ModifyServerOptions(o =>
+ {
+ o.EnableSchemaRequests = false;
+ o.Tool.Enable = false;
+ }));
+ var url = TestServerExtensions.CreateUrl("/graphql/semantic-non-null-schema.graphql");
+ var request = new HttpRequestMessage(HttpMethod.Get, url);
+
+ // act
+ var response = await server.CreateClient().SendAsync(request);
+
+ // assert
+ Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
+ }
+
+ [Fact]
+ public async Task Post_To_SemanticNonNull_Schema_Endpoint_Returns_NotFound()
+ {
+ // arrange
+ var server = CreateStarWarsServer();
+ var url = TestServerExtensions.CreateUrl("/graphql/semantic-non-null-schema.graphql");
+ var request = new HttpRequestMessage(HttpMethod.Post, url);
+
+ // act
+ var response = await server.CreateClient().SendAsync(request);
+
+ // assert
+ Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
+ }
+
+ [Fact]
+ public async Task Download_GraphQL_SemanticNonNull_Schema_Explicit_Pattern()
+ {
+ // arrange
+ var server = CreateServer(b => b.MapGraphQLSemanticNonNullSchema("/foo/bar.graphql"));
+ var url = TestServerExtensions.CreateUrl("/foo/bar.graphql");
+ var request = new HttpRequestMessage(HttpMethod.Get, url);
+
+ // act
+ var response = await server.CreateClient().SendAsync(request);
+
+ // assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ var result = await response.Content.ReadAsStringAsync();
+ result.MatchSnapshot();
+ }
+
+ private sealed class StaticTimeProvider : ITimeProvider
+ {
+ public DateTimeOffset UtcNow { get; } = new(2021, 1, 1, 0, 0, 0, TimeSpan.Zero);
+ }
+}
diff --git a/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/SemanticNonNullSchemaRewriterTests.cs b/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/SemanticNonNullSchemaRewriterTests.cs
new file mode 100644
index 00000000000..e70671156b1
--- /dev/null
+++ b/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/SemanticNonNullSchemaRewriterTests.cs
@@ -0,0 +1,470 @@
+using HotChocolate.AspNetCore.Formatters;
+using HotChocolate.Language;
+
+namespace HotChocolate.AspNetCore;
+
+public class SemanticNonNullSchemaRewriterTests
+{
+ [Fact]
+ public void Rewrite_Should_Return_Same_Document_When_No_NonNull_Fields()
+ {
+ // arrange
+ var schema = Utf8GraphQLParser.Parse(
+ """
+ type Query {
+ hero: Character
+ }
+
+ type Character {
+ name: String
+ }
+ """);
+
+ // act
+ var result = SemanticNonNullSchemaRewriter.Rewrite(schema);
+
+ // assert
+ result.ToString().MatchInlineSnapshot(
+ """
+ type Query {
+ hero: Character
+ }
+
+ type Character {
+ name: String
+ }
+ """);
+ }
+
+ [Fact]
+ public void Rewrite_Should_Strip_NonNull_And_Add_Directive_When_Field_Is_NonNull()
+ {
+ // arrange
+ var schema = Utf8GraphQLParser.Parse(
+ """
+ type Query {
+ name: String!
+ }
+ """);
+
+ // act
+ var result = SemanticNonNullSchemaRewriter.Rewrite(schema);
+
+ // assert
+ result.ToString().MatchInlineSnapshot(
+ """
+ type Query {
+ name: String @semanticNonNull
+ }
+
+ directive @semanticNonNull(levels: [Int!] = [
+ 0
+ ]) on FIELD_DEFINITION
+ """);
+ }
+
+ [Fact]
+ public void Rewrite_Should_Compute_Multiple_Levels_When_NonNull_List_Items()
+ {
+ // arrange
+ var schema = Utf8GraphQLParser.Parse(
+ """
+ type Query {
+ tags: [String!]!
+ matrix: [[String!]!]!
+ innerOnly: [[String!]]
+ }
+ """);
+
+ // act
+ var result = SemanticNonNullSchemaRewriter.Rewrite(schema);
+
+ // assert
+ result.ToString().MatchInlineSnapshot(
+ """
+ type Query {
+ tags: [String] @semanticNonNull(levels: [0, 1])
+ matrix: [[String]] @semanticNonNull(levels: [0, 1, 2])
+ innerOnly: [[String]] @semanticNonNull(levels: [2])
+ }
+
+ directive @semanticNonNull(levels: [Int!] = [
+ 0
+ ]) on FIELD_DEFINITION
+ """);
+ }
+
+ [Fact]
+ public void Rewrite_Should_Skip_Mutation_Type_When_Declared_In_Schema_Definition()
+ {
+ // arrange
+ var schema = Utf8GraphQLParser.Parse(
+ """
+ schema {
+ query: Query
+ mutation: MyMutation
+ }
+
+ type Query {
+ hello: String!
+ }
+
+ type MyMutation {
+ doStuff: String!
+ }
+ """);
+
+ // act
+ var result = SemanticNonNullSchemaRewriter.Rewrite(schema);
+
+ // assert
+ result.ToString().MatchInlineSnapshot(
+ """
+ schema {
+ query: Query
+ mutation: MyMutation
+ }
+
+ type Query {
+ hello: String @semanticNonNull
+ }
+
+ type MyMutation {
+ doStuff: String!
+ }
+
+ directive @semanticNonNull(levels: [Int!] = [
+ 0
+ ]) on FIELD_DEFINITION
+ """);
+ }
+
+ [Fact]
+ public void Rewrite_Should_Skip_Mutation_Type_By_Convention_When_No_Schema_Definition()
+ {
+ // arrange
+ var schema = Utf8GraphQLParser.Parse(
+ """
+ type Query {
+ hello: String!
+ }
+
+ type Mutation {
+ doStuff: String!
+ }
+ """);
+
+ // act
+ var result = SemanticNonNullSchemaRewriter.Rewrite(schema);
+
+ // assert
+ result.ToString().MatchInlineSnapshot(
+ """
+ type Query {
+ hello: String @semanticNonNull
+ }
+
+ type Mutation {
+ doStuff: String!
+ }
+
+ directive @semanticNonNull(levels: [Int!] = [
+ 0
+ ]) on FIELD_DEFINITION
+ """);
+ }
+
+ [Fact]
+ public void Rewrite_Should_Skip_PageInfo_And_CollectionSegmentInfo_Object_Types()
+ {
+ // arrange
+ var schema = Utf8GraphQLParser.Parse(
+ """
+ type Query {
+ hero: String!
+ }
+
+ type PageInfo {
+ hasNextPage: Boolean!
+ }
+
+ type CollectionSegmentInfo {
+ hasNextPage: Boolean!
+ }
+ """);
+
+ // act
+ var result = SemanticNonNullSchemaRewriter.Rewrite(schema);
+
+ // assert
+ result.ToString().MatchInlineSnapshot(
+ """
+ type Query {
+ hero: String @semanticNonNull
+ }
+
+ type PageInfo {
+ hasNextPage: Boolean!
+ }
+
+ type CollectionSegmentInfo {
+ hasNextPage: Boolean!
+ }
+
+ directive @semanticNonNull(levels: [Int!] = [
+ 0
+ ]) on FIELD_DEFINITION
+ """);
+ }
+
+ [Fact]
+ public void Rewrite_Should_Skip_Node_Interface_And_Id_Fields()
+ {
+ // arrange
+ var schema = Utf8GraphQLParser.Parse(
+ """
+ type Query {
+ hero: Character!
+ }
+
+ type Character {
+ id: ID!
+ name: String!
+ }
+
+ interface Node {
+ id: ID!
+ }
+
+ interface HasName {
+ id: ID!
+ name: String!
+ }
+ """);
+
+ // act
+ var result = SemanticNonNullSchemaRewriter.Rewrite(schema);
+
+ // assert
+ result.ToString().MatchInlineSnapshot(
+ """
+ type Query {
+ hero: Character @semanticNonNull
+ }
+
+ type Character {
+ id: ID!
+ name: String @semanticNonNull
+ }
+
+ interface Node {
+ id: ID!
+ }
+
+ interface HasName {
+ id: ID!
+ name: String @semanticNonNull
+ }
+
+ directive @semanticNonNull(levels: [Int!] = [
+ 0
+ ]) on FIELD_DEFINITION
+ """);
+ }
+
+ [Fact]
+ public void Rewrite_Should_Skip_Introspection_Types_And_Fields()
+ {
+ // arrange
+ var schema = Utf8GraphQLParser.Parse(
+ """
+ type Query {
+ hero: String!
+ __typename: String!
+ }
+
+ type __Schema {
+ types: [String!]!
+ }
+ """);
+
+ // act
+ var result = SemanticNonNullSchemaRewriter.Rewrite(schema);
+
+ // assert
+ result.ToString().MatchInlineSnapshot(
+ """
+ type Query {
+ hero: String @semanticNonNull
+ __typename: String!
+ }
+
+ type __Schema {
+ types: [String!]!
+ }
+
+ directive @semanticNonNull(levels: [Int!] = [
+ 0
+ ]) on FIELD_DEFINITION
+ """);
+ }
+
+ [Fact]
+ public void Rewrite_Should_Preserve_Existing_Field_Directives_When_Adding_SemanticNonNull()
+ {
+ // arrange
+ var schema = Utf8GraphQLParser.Parse(
+ """
+ type Query {
+ hello: String! @deprecated(reason: "old")
+ }
+ """);
+
+ // act
+ var result = SemanticNonNullSchemaRewriter.Rewrite(schema);
+
+ // assert
+ result.ToString().MatchInlineSnapshot(
+ """
+ type Query {
+ hello: String @deprecated(reason: "old") @semanticNonNull
+ }
+
+ directive @semanticNonNull(levels: [Int!] = [
+ 0
+ ]) on FIELD_DEFINITION
+ """);
+ }
+
+ [Fact]
+ public void Rewrite_Should_Insert_Directive_Alphabetically_When_Existing_Directive_Definitions()
+ {
+ // arrange
+ var schema = Utf8GraphQLParser.Parse(
+ """
+ type Query {
+ hello: String!
+ }
+
+ directive @cost(weight: String!) on FIELD_DEFINITION
+ directive @listSize(assumedSize: Int) on FIELD_DEFINITION
+ directive @stream on FIELD
+ """);
+
+ // act
+ var result = SemanticNonNullSchemaRewriter.Rewrite(schema);
+
+ // assert
+ result.ToString().MatchInlineSnapshot(
+ """
+ type Query {
+ hello: String @semanticNonNull
+ }
+
+ directive @cost(weight: String!) on FIELD_DEFINITION
+
+ directive @listSize(assumedSize: Int) on FIELD_DEFINITION
+
+ directive @semanticNonNull(levels: [Int!] = [
+ 0
+ ]) on FIELD_DEFINITION
+
+ directive @stream on FIELD
+ """);
+ }
+
+ [Fact]
+ public void Rewrite_Should_Insert_Directive_Between_Enums_And_Scalars_When_No_Existing_Directives()
+ {
+ // arrange
+ var schema = Utf8GraphQLParser.Parse(
+ """
+ type Query {
+ hello: String!
+ }
+
+ enum Episode {
+ NEW_HOPE
+ }
+
+ scalar DateTime
+ """);
+
+ // act
+ var result = SemanticNonNullSchemaRewriter.Rewrite(schema);
+
+ // assert
+ result.ToString().MatchInlineSnapshot(
+ """
+ type Query {
+ hello: String @semanticNonNull
+ }
+
+ enum Episode {
+ NEW_HOPE
+ }
+
+ directive @semanticNonNull(levels: [Int!] = [
+ 0
+ ]) on FIELD_DEFINITION
+
+ scalar DateTime
+ """);
+ }
+
+ [Fact]
+ public void Rewrite_Should_Not_Duplicate_Directive_Definition_When_Already_Defined()
+ {
+ // arrange
+ var schema = Utf8GraphQLParser.Parse(
+ """
+ type Query {
+ hello: String!
+ }
+
+ directive @semanticNonNull(levels: [Int!] = [0]) on FIELD_DEFINITION
+ """);
+
+ // act
+ var result = SemanticNonNullSchemaRewriter.Rewrite(schema);
+
+ // assert
+ result.ToString().MatchInlineSnapshot(
+ """
+ type Query {
+ hello: String @semanticNonNull
+ }
+
+ directive @semanticNonNull(levels: [Int!] = [
+ 0
+ ]) on FIELD_DEFINITION
+ """);
+ }
+
+ [Fact]
+ public void Rewrite_Should_Replace_Existing_SemanticNonNull_Directive_On_Field()
+ {
+ // arrange
+ var schema = Utf8GraphQLParser.Parse(
+ """
+ type Query {
+ tags: [String!]! @semanticNonNull(levels: [42])
+ }
+ """);
+
+ // act
+ var result = SemanticNonNullSchemaRewriter.Rewrite(schema);
+
+ // assert
+ result.ToString().MatchInlineSnapshot(
+ """
+ type Query {
+ tags: [String] @semanticNonNull(levels: [0, 1])
+ }
+
+ directive @semanticNonNull(levels: [Int!] = [
+ 0
+ ]) on FIELD_DEFINITION
+ """);
+ }
+}
diff --git a/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/__snapshots__/HttpGetSemanticNonNullSchemaMiddlewareTests.Download_GraphQL_SemanticNonNull_Schema.md b/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/__snapshots__/HttpGetSemanticNonNullSchemaMiddlewareTests.Download_GraphQL_SemanticNonNull_Schema.md
new file mode 100644
index 00000000000..244a577dbf7
--- /dev/null
+++ b/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/__snapshots__/HttpGetSemanticNonNullSchemaMiddlewareTests.Download_GraphQL_SemanticNonNull_Schema.md
@@ -0,0 +1,223 @@
+# Download_GraphQL_SemanticNonNull_Schema
+
+```text
+Headers:
+Cache-Control: public, must-revalidate, max-age=3600
+Content-Type: application/graphql; charset=utf-8
+Content-Disposition: attachment; filename="schema.graphql"
+Last-Modified: Fri, 01 Jan 2021 00:00:00 GMT
+-------------------------->
+Status Code: OK
+-------------------------->
+schema {
+ query: Query
+ mutation: Mutation
+ subscription: Subscription
+}
+
+interface Character {
+ id: ID!
+ name: String @semanticNonNull
+ friends(
+ "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
+ ): FriendsConnection
+ appearsIn: [Episode]
+ traits: Any
+ height(unit: Unit): Float
+}
+
+type Droid implements Character {
+ id: ID!
+ name: String @semanticNonNull
+ appearsIn: [Episode]
+ friends(
+ "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
+ ): FriendsConnection
+ @listSize(assumedSize: 50, slicingArguments: ["first", "last"], slicingArgumentDefaultValue: 10, sizedFields: ["edges", "nodes"], requireOneSlicingArgument: false)
+ @cost(weight: "10")
+ height(unit: Unit): Float
+ primaryFunction: String
+ traits: Any
+}
+
+"A connection to a list of items."
+type FriendsConnection {
+ "Information to aid in pagination."
+ pageInfo: PageInfo @semanticNonNull
+ "A list of edges."
+ edges: [FriendsEdge] @semanticNonNull(levels: [1])
+ "A flattened list of the nodes."
+ nodes: [Character]
+}
+
+"An edge in a connection."
+type FriendsEdge {
+ "A cursor for use in pagination."
+ cursor: String @semanticNonNull
+ "The item at the end of the edge."
+ node: Character
+}
+
+type Human implements Character {
+ id: ID!
+ name: String @semanticNonNull
+ appearsIn: [Episode]
+ friends(
+ "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
+ ): FriendsConnection
+ @listSize(assumedSize: 50, slicingArguments: ["first", "last"], slicingArgumentDefaultValue: 10, sizedFields: ["edges", "nodes"], requireOneSlicingArgument: false)
+ @cost(weight: "10")
+ otherHuman: Human
+ height(unit: Unit): Float
+ homePlanet: String
+ traits: Any
+}
+
+type Mutation {
+ createReview(episode: Episode!, review: ReviewInput!): Review!
+ @cost(weight: "10")
+ complete(episode: Episode!): Boolean! @cost(weight: "10")
+}
+
+"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
+}
+
+type Query {
+ hero(episode: Episode! = NEW_HOPE): Character
+ heroByTraits(traits: Any!): Character
+ heroes(episodes: [Episode!]!): [Character] @semanticNonNull(levels: [1])
+ character(characterIds: [String!]!): [Character]
+ @cost(weight: "10")
+ @semanticNonNull(levels: [0, 1])
+ search(text: String!): [SearchResult]
+ human(id: String!): Human
+ droid(id: String!): Droid
+ time: Long @semanticNonNull
+ evict: Boolean @semanticNonNull
+ wait(m: Int!): Boolean @cost(weight: "10") @semanticNonNull
+ someDeprecatedField(
+ deprecatedArg: String! = "foo" @deprecated(reason: "use something else")
+ ): String @deprecated(reason: "use something else") @semanticNonNull
+}
+
+type Review {
+ commentary: String @cost(weight: "10")
+ stars: Int @semanticNonNull
+}
+
+type Starship {
+ id: ID!
+ name: String @semanticNonNull
+ length(unit: Unit): Float @semanticNonNull
+}
+
+type Subscription {
+ onReview(episode: Episode!): Review @semanticNonNull
+ delay(delay: Int!, count: Int!): String @semanticNonNull
+}
+
+union SearchResult = Starship | Human | Droid
+
+input ReviewInput {
+ stars: Int!
+ commentary: String
+}
+
+enum Episode {
+ NEW_HOPE
+ EMPIRE
+ JEDI
+}
+
+enum Unit {
+ FOOT
+ METERS
+}
+
+"The purpose of the `cost` directive is to define a `weight` for GraphQL types, fields, and arguments. Static analysis can use these weights when calculating the overall cost of a query or response."
+directive @cost(
+ "The `weight` argument defines what value to add to the overall cost for every appearance, or possible appearance, of a type, field, argument, etc."
+ weight: String!
+) on SCALAR | OBJECT | FIELD_DEFINITION | ARGUMENT_DEFINITION | ENUM | INPUT_FIELD_DEFINITION
+
+"The `@defer` directive may be provided for fragment spreads and inline fragments to inform the executor to delay the execution of the current fragment to indicate deprioritization of the current fragment. A query with `@defer` directive will cause the request to potentially return multiple responses, where non-deferred data is delivered in the initial response and data deferred is delivered in a subsequent response. `@include` and `@skip` take precedence over `@defer`."
+directive @defer(
+ "If this argument label has a value other than null, it will be passed on to the result of this defer directive. This label is intended to give client applications a way to identify to which fragment a deferred result belongs to."
+ label: String
+ "Deferred when true."
+ if: Boolean
+) on FRAGMENT_SPREAD | INLINE_FRAGMENT
+
+directive @foo(bar: Int!) on SUBSCRIPTION
+
+"The purpose of the `@listSize` directive is to either inform the static analysis about the size of returned lists (if that information is statically available), or to point the analysis to where to find that information."
+directive @listSize(
+ "The `assumedSize` argument can be used to statically define the maximum length of a list returned by a field."
+ assumedSize: Int
+ "The `slicingArguments` argument can be used to define which of the field's arguments with numeric type are slicing arguments, so that their value determines the size of the list returned by that field. It may specify a list of multiple slicing arguments."
+ slicingArguments: [String!]
+ "The `slicingArgumentDefaultValue` argument can be used to define a default value for a slicing argument, which is used if the argument is not present in a query."
+ slicingArgumentDefaultValue: Int
+ "The `sizedFields` argument can be used to define that the value of the `assumedSize` argument or of a slicing argument does not affect the size of a list returned by a field itself, but that of a list returned by one of its sub-fields."
+ sizedFields: [String!]
+ "The `requireOneSlicingArgument` argument can be used to inform the static analysis that it should expect that exactly one of the defined slicing arguments is present in a query. If that is not the case (i.e., if none or multiple slicing arguments are present), the static analysis may throw an error."
+ requireOneSlicingArgument: Boolean = true
+) on FIELD_DEFINITION
+
+directive @semanticNonNull(levels: [Int!] = [
+ 0
+]) on FIELD_DEFINITION
+
+"The `@specifiedBy` directive is used within the type system definition language to provide a URL for specifying the behavior of custom scalar definitions."
+directive @specifiedBy(
+ "The specifiedBy URL points to a human-readable specification. This field will only read a result for scalar types."
+ url: String!
+) on SCALAR
+
+"The `@stream` directive may be provided for a field of `List` type so that the backend can leverage technology such as asynchronous iterators to provide a partial list in the initial response, and additional list items in subsequent responses. `@include` and `@skip` take precedence over `@stream`."
+directive @stream(
+ "If this argument label has a value other than null, it will be passed on to the result of this stream directive. This label is intended to give client applications a way to identify to which fragment a streamed result belongs to."
+ label: String
+ "The initial elements that shall be send down to the consumer."
+ initialCount: Int! = 0
+ "Streamed when true."
+ if: Boolean
+) on FIELD
+
+"The `Any` scalar type represents any valid GraphQL value."
+scalar Any @specifiedBy(url: "https://scalars.graphql.org/chillicream/any.html")
+
+"The `Long` scalar type represents a signed 64-bit integer."
+scalar Long
+ @specifiedBy(url: "https://scalars.graphql.org/chillicream/long.html")
+```
diff --git a/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/__snapshots__/HttpGetSemanticNonNullSchemaMiddlewareTests.Download_GraphQL_SemanticNonNull_Schema_Explicit_Pattern.snap b/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/__snapshots__/HttpGetSemanticNonNullSchemaMiddlewareTests.Download_GraphQL_SemanticNonNull_Schema_Explicit_Pattern.snap
new file mode 100644
index 00000000000..1dfffa496a2
--- /dev/null
+++ b/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/__snapshots__/HttpGetSemanticNonNullSchemaMiddlewareTests.Download_GraphQL_SemanticNonNull_Schema_Explicit_Pattern.snap
@@ -0,0 +1,209 @@
+schema {
+ query: Query
+ mutation: Mutation
+ subscription: Subscription
+}
+
+interface Character {
+ id: ID!
+ name: String @semanticNonNull
+ friends(
+ "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
+ ): FriendsConnection
+ appearsIn: [Episode]
+ traits: Any
+ height(unit: Unit): Float
+}
+
+type Droid implements Character {
+ id: ID!
+ name: String @semanticNonNull
+ appearsIn: [Episode]
+ friends(
+ "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
+ ): FriendsConnection
+ @listSize(assumedSize: 50, slicingArguments: ["first", "last"], slicingArgumentDefaultValue: 10, sizedFields: ["edges", "nodes"], requireOneSlicingArgument: false)
+ @cost(weight: "10")
+ height(unit: Unit): Float
+ primaryFunction: String
+ traits: Any
+}
+
+"A connection to a list of items."
+type FriendsConnection {
+ "Information to aid in pagination."
+ pageInfo: PageInfo @semanticNonNull
+ "A list of edges."
+ edges: [FriendsEdge] @semanticNonNull(levels: [1])
+ "A flattened list of the nodes."
+ nodes: [Character]
+}
+
+"An edge in a connection."
+type FriendsEdge {
+ "A cursor for use in pagination."
+ cursor: String @semanticNonNull
+ "The item at the end of the edge."
+ node: Character
+}
+
+type Human implements Character {
+ id: ID!
+ name: String @semanticNonNull
+ appearsIn: [Episode]
+ friends(
+ "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
+ ): FriendsConnection
+ @listSize(assumedSize: 50, slicingArguments: ["first", "last"], slicingArgumentDefaultValue: 10, sizedFields: ["edges", "nodes"], requireOneSlicingArgument: false)
+ @cost(weight: "10")
+ otherHuman: Human
+ height(unit: Unit): Float
+ homePlanet: String
+ traits: Any
+}
+
+type Mutation {
+ createReview(episode: Episode!, review: ReviewInput!): Review!
+ @cost(weight: "10")
+ complete(episode: Episode!): Boolean! @cost(weight: "10")
+}
+
+"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
+}
+
+type Query {
+ hero(episode: Episode! = NEW_HOPE): Character
+ heroByTraits(traits: Any!): Character
+ heroes(episodes: [Episode!]!): [Character] @semanticNonNull(levels: [1])
+ character(characterIds: [String!]!): [Character]
+ @cost(weight: "10")
+ @semanticNonNull(levels: [0, 1])
+ search(text: String!): [SearchResult]
+ human(id: String!): Human
+ droid(id: String!): Droid
+ time: Long @semanticNonNull
+ evict: Boolean @semanticNonNull
+ wait(m: Int!): Boolean @cost(weight: "10") @semanticNonNull
+ someDeprecatedField(
+ deprecatedArg: String! = "foo" @deprecated(reason: "use something else")
+ ): String @deprecated(reason: "use something else") @semanticNonNull
+}
+
+type Review {
+ commentary: String @cost(weight: "10")
+ stars: Int @semanticNonNull
+}
+
+type Starship {
+ id: ID!
+ name: String @semanticNonNull
+ length(unit: Unit): Float @semanticNonNull
+}
+
+type Subscription {
+ onReview(episode: Episode!): Review @semanticNonNull
+ delay(delay: Int!, count: Int!): String @semanticNonNull
+}
+
+union SearchResult = Starship | Human | Droid
+
+input ReviewInput {
+ stars: Int!
+ commentary: String
+}
+
+enum Episode {
+ NEW_HOPE
+ EMPIRE
+ JEDI
+}
+
+enum Unit {
+ FOOT
+ METERS
+}
+
+"The purpose of the `cost` directive is to define a `weight` for GraphQL types, fields, and arguments. Static analysis can use these weights when calculating the overall cost of a query or response."
+directive @cost(
+ "The `weight` argument defines what value to add to the overall cost for every appearance, or possible appearance, of a type, field, argument, etc."
+ weight: String!
+) on SCALAR | OBJECT | FIELD_DEFINITION | ARGUMENT_DEFINITION | ENUM | INPUT_FIELD_DEFINITION
+
+"The `@defer` directive may be provided for fragment spreads and inline fragments to inform the executor to delay the execution of the current fragment to indicate deprioritization of the current fragment. A query with `@defer` directive will cause the request to potentially return multiple responses, where non-deferred data is delivered in the initial response and data deferred is delivered in a subsequent response. `@include` and `@skip` take precedence over `@defer`."
+directive @defer(
+ "If this argument label has a value other than null, it will be passed on to the result of this defer directive. This label is intended to give client applications a way to identify to which fragment a deferred result belongs to."
+ label: String
+ "Deferred when true."
+ if: Boolean
+) on FRAGMENT_SPREAD | INLINE_FRAGMENT
+
+"The purpose of the `@listSize` directive is to either inform the static analysis about the size of returned lists (if that information is statically available), or to point the analysis to where to find that information."
+directive @listSize(
+ "The `assumedSize` argument can be used to statically define the maximum length of a list returned by a field."
+ assumedSize: Int
+ "The `slicingArguments` argument can be used to define which of the field's arguments with numeric type are slicing arguments, so that their value determines the size of the list returned by that field. It may specify a list of multiple slicing arguments."
+ slicingArguments: [String!]
+ "The `slicingArgumentDefaultValue` argument can be used to define a default value for a slicing argument, which is used if the argument is not present in a query."
+ slicingArgumentDefaultValue: Int
+ "The `sizedFields` argument can be used to define that the value of the `assumedSize` argument or of a slicing argument does not affect the size of a list returned by a field itself, but that of a list returned by one of its sub-fields."
+ sizedFields: [String!]
+ "The `requireOneSlicingArgument` argument can be used to inform the static analysis that it should expect that exactly one of the defined slicing arguments is present in a query. If that is not the case (i.e., if none or multiple slicing arguments are present), the static analysis may throw an error."
+ requireOneSlicingArgument: Boolean = true
+) on FIELD_DEFINITION
+
+directive @semanticNonNull(levels: [Int!] = [
+ 0
+]) on FIELD_DEFINITION
+
+"The `@specifiedBy` directive is used within the type system definition language to provide a URL for specifying the behavior of custom scalar definitions."
+directive @specifiedBy(
+ "The specifiedBy URL points to a human-readable specification. This field will only read a result for scalar types."
+ url: String!
+) on SCALAR
+
+"The `@stream` directive may be provided for a field of `List` type so that the backend can leverage technology such as asynchronous iterators to provide a partial list in the initial response, and additional list items in subsequent responses. `@include` and `@skip` take precedence over `@stream`."
+directive @stream(
+ "If this argument label has a value other than null, it will be passed on to the result of this stream directive. This label is intended to give client applications a way to identify to which fragment a streamed result belongs to."
+ label: String
+ "The initial elements that shall be send down to the consumer."
+ initialCount: Int! = 0
+ "Streamed when true."
+ if: Boolean
+) on FIELD
+
+"The `Any` scalar type represents any valid GraphQL value."
+scalar Any @specifiedBy(url: "https://scalars.graphql.org/chillicream/any.html")
+
+"The `Long` scalar type represents a signed 64-bit integer."
+scalar Long
+ @specifiedBy(url: "https://scalars.graphql.org/chillicream/long.html")
diff --git a/src/HotChocolate/Core/src/Abstractions/WellKnownMiddleware.cs b/src/HotChocolate/Core/src/Abstractions/WellKnownMiddleware.cs
index be6fa2bb050..d7d31265e9d 100644
--- a/src/HotChocolate/Core/src/Abstractions/WellKnownMiddleware.cs
+++ b/src/HotChocolate/Core/src/Abstractions/WellKnownMiddleware.cs
@@ -90,9 +90,4 @@ public static class WellKnownMiddleware
/// The key identifies the authorization middleware.
///
public const string Authorization = "HotChocolate.Authorization";
-
- ///
- /// This key identifies the semantic-non-null middleware.
- ///
- public const string SemanticNonNull = "HotChocolate.Types.SemanticNonNull";
}
diff --git a/src/HotChocolate/Core/src/Types/Configuration/BuiltInDirectiveTypeReferences.cs b/src/HotChocolate/Core/src/Types/Configuration/BuiltInDirectiveTypeReferences.cs
index b7acb062173..9935bf96698 100644
--- a/src/HotChocolate/Core/src/Types/Configuration/BuiltInDirectiveTypeReferences.cs
+++ b/src/HotChocolate/Core/src/Types/Configuration/BuiltInDirectiveTypeReferences.cs
@@ -22,11 +22,6 @@ internal static void Enqueue(
EnqueueTypeRef(backlog, typeInspector.GetTypeRef(typeof(StreamDirectiveType)), nextIndex++);
}
- if (context.Options.EnableSemanticNonNull)
- {
- EnqueueTypeRef(backlog, typeInspector.GetTypeRef(typeof(SemanticNonNullDirective)), nextIndex++);
- }
-
if (context.Options.EnableTag)
{
EnqueueTypeRef(backlog, typeInspector.GetTypeRef(typeof(Tag)), nextIndex++);
diff --git a/src/HotChocolate/Core/src/Types/IReadOnlySchemaOptions.cs b/src/HotChocolate/Core/src/Types/IReadOnlySchemaOptions.cs
index 8b998e03460..aca891a0d20 100644
--- a/src/HotChocolate/Core/src/Types/IReadOnlySchemaOptions.cs
+++ b/src/HotChocolate/Core/src/Types/IReadOnlySchemaOptions.cs
@@ -154,13 +154,6 @@ public interface IReadOnlySchemaOptions
///
bool EnableStream { get; }
- ///
- /// Enables the @semanticNonNull directive and rewrites Non-Null types to nullable types
- /// with this directive attached to indicate semantic non-nullability.
- /// This feature is experimental and might be changed or removed in the future.
- ///
- bool EnableSemanticNonNull { get; }
-
///
/// Specified if the leading I shall be stripped from the interface name.
///
diff --git a/src/HotChocolate/Core/src/Types/SchemaBuilder.cs b/src/HotChocolate/Core/src/Types/SchemaBuilder.cs
index 8eab2ffd490..e28c1340d02 100644
--- a/src/HotChocolate/Core/src/Types/SchemaBuilder.cs
+++ b/src/HotChocolate/Core/src/Types/SchemaBuilder.cs
@@ -33,7 +33,6 @@ private SchemaBuilder()
typeInterceptors.TryAdd(new IntrospectionTypeInterceptor());
typeInterceptors.TryAdd(new InterfaceCompletionTypeInterceptor());
typeInterceptors.TryAdd(new MiddlewareValidationTypeInterceptor());
- typeInterceptors.TryAdd(new SemanticNonNullTypeInterceptor());
typeInterceptors.TryAdd(new StoreGlobalPagingOptionsTypeInterceptor());
typeInterceptors.TryAdd(new StoreGlobalSchemaOptionsTypeInterceptor());
typeInterceptors.TryAdd(new OptInFeaturesTypeInterceptor());
diff --git a/src/HotChocolate/Core/src/Types/SchemaOptions.cs b/src/HotChocolate/Core/src/Types/SchemaOptions.cs
index 5facbbec106..cfaad846b84 100644
--- a/src/HotChocolate/Core/src/Types/SchemaOptions.cs
+++ b/src/HotChocolate/Core/src/Types/SchemaOptions.cs
@@ -104,9 +104,6 @@ public FieldBindingFlags DefaultFieldBindingFlags
///
public bool EnableStream { get; set; }
- ///
- public bool EnableSemanticNonNull { get; set; }
-
///
public bool StripLeadingIFromInterface { get; set; }
@@ -224,7 +221,6 @@ public static SchemaOptions FromOptions(IReadOnlySchemaOptions options)
EnableFlagEnums = options.EnableFlagEnums,
EnableDefer = options.EnableDefer,
EnableStream = options.EnableStream,
- EnableSemanticNonNull = options.EnableSemanticNonNull,
DefaultFieldBindingFlags = options.DefaultFieldBindingFlags,
StripLeadingIFromInterface = options.StripLeadingIFromInterface,
EnableTag = options.EnableTag,
diff --git a/src/HotChocolate/Core/src/Types/SemanticNonNullTypeInterceptor.cs b/src/HotChocolate/Core/src/Types/SemanticNonNullTypeInterceptor.cs
deleted file mode 100644
index c8ed8c40ca6..00000000000
--- a/src/HotChocolate/Core/src/Types/SemanticNonNullTypeInterceptor.cs
+++ /dev/null
@@ -1,346 +0,0 @@
-using System.Collections;
-using HotChocolate.Configuration;
-using HotChocolate.Internal;
-using HotChocolate.Language;
-using HotChocolate.Resolvers;
-using HotChocolate.Types;
-using HotChocolate.Types.Descriptors;
-using HotChocolate.Types.Descriptors.Configurations;
-
-namespace HotChocolate;
-
-internal sealed class SemanticNonNullTypeInterceptor : TypeInterceptor
-{
- private ITypeInspector _typeInspector = null!;
- private ObjectTypeConfiguration? _mutationDef;
-
- public override bool IsEnabled(IDescriptorContext context)
- => context.Options.EnableSemanticNonNull;
-
- internal override void InitializeContext(
- IDescriptorContext context,
- TypeInitializer typeInitializer,
- TypeRegistry typeRegistry,
- TypeLookup typeLookup,
- TypeReferenceResolver typeReferenceResolver)
- {
- _typeInspector = context.TypeInspector;
- }
-
- public override void OnAfterResolveRootType(ITypeCompletionContext completionContext, ObjectTypeConfiguration configuration,
- OperationType operationType)
- {
- if (operationType is OperationType.Mutation)
- {
- _mutationDef = configuration;
- }
- }
-
- public override void OnBeforeCompleteType(ITypeCompletionContext completionContext, TypeSystemConfiguration configuration)
- {
- if (completionContext.IsIntrospectionType)
- {
- return;
- }
-
- if (configuration is ObjectTypeConfiguration objectDef)
- {
- if (objectDef.Name is "CollectionSegmentInfo" or "PageInfo")
- {
- return;
- }
-
- // We undo semantic non-nullability on each mutation field, since mutations can be chained
- // and we want to retain the null-bubbling so execution is aborted if a non-null mutation field
- // produces an error.
- if (objectDef == _mutationDef)
- {
- return;
- }
-
- foreach (var field in objectDef.Fields)
- {
- if (field.IsIntrospectionField)
- {
- continue;
- }
-
- if (field.Name == "id")
- {
- continue;
- }
-
- if (field.Type is null)
- {
- continue;
- }
-
- var levels = GetSemanticNonNullLevels(field.Type);
-
- if (levels.Count < 1)
- {
- continue;
- }
-
- ApplySemanticNonNullDirective(field, completionContext, levels);
-
- field.FormatterConfigurations.Add(CreateSemanticNonNullResultFormatterConfiguration(levels));
- }
- }
- else if (configuration is InterfaceTypeConfiguration 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)
- {
- if (field.Type is null)
- {
- continue;
- }
-
- if (field.Name == "id")
- {
- continue;
- }
-
- var levels = GetSemanticNonNullLevels(field.Type);
-
- if (levels.Count < 1)
- {
- continue;
- }
-
- ApplySemanticNonNullDirective(field, completionContext, levels);
- }
- }
- }
-
- private void ApplySemanticNonNullDirective(
- OutputFieldConfiguration field,
- ITypeCompletionContext completionContext,
- HashSet levels)
- {
- var directiveDependency = new TypeDependency(
- _typeInspector.GetTypeRef(typeof(SemanticNonNullDirective)),
- TypeDependencyFulfilled.Completed);
-
- ((RegisteredType)completionContext).Dependencies.Add(directiveDependency);
-
- field.AddDirective(new SemanticNonNullDirective(levels.ToList()), _typeInspector);
-
- field.Type = BuildNullableTypeStructure(field.Type!, _typeInspector);
- }
-
- private static HashSet GetSemanticNonNullLevels(TypeReference typeReference)
- {
- if (typeReference is ExtendedTypeReference extendedTypeReference)
- {
- return GetSemanticNonNullLevelsFromReference(extendedTypeReference);
- }
-
- if (typeReference is SchemaTypeReference schemaRef)
- {
- return GetSemanticNonNullLevelsFromReference(schemaRef);
- }
-
- if (typeReference is SyntaxTypeReference syntaxRef)
- {
- return GetSemanticNonNullLevelsFromReference(syntaxRef);
- }
-
- return [];
- }
-
- private static HashSet GetSemanticNonNullLevelsFromReference(ExtendedTypeReference typeReference)
- {
- var levels = new HashSet();
-
- var currentType = typeReference.Type;
- var index = 0;
-
- do
- {
- if (currentType.IsArrayOrList)
- {
- if (!currentType.IsNullable)
- {
- levels.Add(index);
- }
-
- index++;
- currentType = currentType.ElementType;
- }
- else if (!currentType.IsNullable)
- {
- levels.Add(index);
- break;
- }
- else
- {
- break;
- }
- } while (currentType is not null);
-
- return levels;
- }
-
- private static HashSet GetSemanticNonNullLevelsFromReference(SchemaTypeReference typeReference)
- {
- var levels = new HashSet();
-
- var currentType = typeReference.Type;
- var index = 0;
-
- while (true)
- {
- if (currentType is ListType listType)
- {
- index++;
- currentType = listType.ElementType;
- }
- else if (currentType is NonNullType nonNullType)
- {
- levels.Add(index);
- currentType = nonNullType.NullableType;
- }
- else
- {
- break;
- }
- }
-
- return levels;
- }
-
- private static HashSet GetSemanticNonNullLevelsFromReference(SyntaxTypeReference typeReference)
- {
- var levels = new HashSet();
-
- var currentType = typeReference.Type;
- var index = 0;
-
- while (true)
- {
- if (currentType is ListTypeNode listType)
- {
- index++;
- currentType = listType.Type;
- }
- else if (currentType is NonNullTypeNode nonNullType)
- {
- levels.Add(index);
- currentType = nonNullType.Type;
- }
- else
- {
- break;
- }
- }
-
- return levels;
- }
-
- private static readonly bool?[] s_fullNullablePattern = Enumerable.Range(0, 32).Select(_ => (bool?)true).ToArray();
-
- private static TypeReference BuildNullableTypeStructure(
- TypeReference typeReference,
- ITypeInspector typeInspector)
- {
- if (typeReference is ExtendedTypeReference extendedTypeRef)
- {
- return extendedTypeRef.WithType(typeInspector.ChangeNullability(extendedTypeRef.Type,
- s_fullNullablePattern));
- }
-
- if (typeReference is SchemaTypeReference schemaRef)
- {
- return schemaRef.WithType(BuildNullableTypeStructure(schemaRef.Type));
- }
-
- if (typeReference is SyntaxTypeReference syntaxRef)
- {
- return syntaxRef.WithType(BuildNullableTypeStructure(syntaxRef.Type));
- }
-
- throw new NotSupportedException();
- }
-
- private static IType BuildNullableTypeStructure(ITypeSystemMember typeSystemMember)
- {
- if (typeSystemMember is ListType listType)
- {
- return new ListType(BuildNullableTypeStructure(listType.ElementType));
- }
-
- if (typeSystemMember is NonNullType nonNullType)
- {
- return BuildNullableTypeStructure(nonNullType.NullableType);
- }
-
- return (IType)typeSystemMember;
- }
-
- private static ITypeNode BuildNullableTypeStructure(ITypeNode typeNode)
- {
- if (typeNode is ListTypeNode listType)
- {
- return new ListTypeNode(BuildNullableTypeStructure(listType.Type));
- }
-
- if (typeNode is NonNullTypeNode nonNullType)
- {
- return BuildNullableTypeStructure(nonNullType.Type);
- }
-
- return typeNode;
- }
-
- private static ResultFormatterConfiguration CreateSemanticNonNullResultFormatterConfiguration(HashSet levels)
- => new((context, result) =>
- {
- CheckResultForSemanticNonNullViolations(result, context, context.Path, levels, 0);
-
- return result;
- },
- isRepeatable: false,
- key: WellKnownMiddleware.SemanticNonNull);
-
- private static void CheckResultForSemanticNonNullViolations(object? result, IResolverContext context, Path path,
- HashSet levels,
- int currentLevel)
- {
- if (result is null && levels.Contains(currentLevel))
- {
- context.ReportError(CreateSemanticNonNullViolationError(path));
- return;
- }
-
- if (result is IEnumerable enumerable)
- {
- if (currentLevel >= 32)
- {
- // We bail if we're at a depth of 32 as this would mean that we're dealing with an AnyType or another structure.
- return;
- }
-
- var index = 0;
- foreach (var item in enumerable)
- {
- CheckResultForSemanticNonNullViolations(item, context, path.Append(index), levels, currentLevel + 1);
-
- index++;
- }
- }
- }
-
- private static IError CreateSemanticNonNullViolationError(Path path)
- => ErrorBuilder.New()
- .SetMessage("Cannot return null for semantic non-null field.")
- .SetCode(ErrorCodes.Execution.SemanticNonNullViolation)
- .SetPath(path)
- .Build();
-}
diff --git a/src/HotChocolate/Core/src/Types/Types/Directives/SemanticNonNullDirective.cs b/src/HotChocolate/Core/src/Types/Types/Directives/SemanticNonNullDirective.cs
deleted file mode 100644
index 2813adaff72..00000000000
--- a/src/HotChocolate/Core/src/Types/Types/Directives/SemanticNonNullDirective.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace HotChocolate.Types;
-
-[DirectiveType(DirectiveNames.SemanticNonNull.Name, DirectiveLocation.FieldDefinition, IsRepeatable = false)]
-public sealed class SemanticNonNullDirective(IReadOnlyList levels)
-{
- [GraphQLType>>]
- [DefaultValueSyntax("[0]")]
- public IReadOnlyList? Levels { get; } = levels;
-}
diff --git a/src/HotChocolate/Core/test/Execution.Tests/SemanticNonNullTests.cs b/src/HotChocolate/Core/test/Execution.Tests/SemanticNonNullTests.cs
deleted file mode 100644
index 04a85a98a49..00000000000
--- a/src/HotChocolate/Core/test/Execution.Tests/SemanticNonNullTests.cs
+++ /dev/null
@@ -1,936 +0,0 @@
-using HotChocolate.Resolvers;
-using HotChocolate.Types;
-using Microsoft.Extensions.DependencyInjection;
-
-namespace HotChocolate.Execution;
-
-public class SemanticNonNullTests
-{
- #region Scalar
-
- [Fact]
- public async Task Async_Scalar_Returns_Null_Should_Error()
- {
- var result = await new ServiceCollection()
- .AddGraphQL()
- .ModifyOptions(o => o.EnableSemanticNonNull = true)
- .AddQueryType()
- .ExecuteRequestAsync("""
- {
- scalarReturningNull
- }
- """);
-
- result.MatchSnapshot();
- }
-
- [Fact]
- public async Task Async_Scalar_Throwing_Should_Null_And_Error()
- {
- var result = await new ServiceCollection()
- .AddGraphQL()
- .ModifyOptions(o => o.EnableSemanticNonNull = true)
- .AddQueryType()
- .ExecuteRequestAsync("""
- {
- scalarThrowingError
- }
- """);
-
- result.MatchSnapshot();
- }
-
- [Fact]
- public async Task Async_Nullable_Scalar_Returns_Null_Should_Null_Without_Error()
- {
- var result = await new ServiceCollection()
- .AddGraphQL()
- .ModifyOptions(o => o.EnableSemanticNonNull = true)
- .AddQueryType()
- .ExecuteRequestAsync("""
- {
- nullableScalarReturningNull
- }
- """);
-
- result.MatchSnapshot();
- }
-
- [Fact]
- public async Task Pure_Scalar_Returns_Null_Should_Error()
- {
- var result = await new ServiceCollection()
- .AddGraphQL()
- .ModifyOptions(o => o.EnableSemanticNonNull = true)
- .AddQueryType()
- .ExecuteRequestAsync("""
- {
- pureScalarReturningNull
- }
- """);
-
- result.MatchSnapshot();
- }
-
- [Fact]
- public async Task Pure_Scalar_Throwing_Should_Null_And_Error()
- {
- var result = await new ServiceCollection()
- .AddGraphQL()
- .ModifyOptions(o => o.EnableSemanticNonNull = true)
- .AddQueryType()
- .ExecuteRequestAsync("""
- {
- pureScalarThrowingError
- }
- """);
-
- result.MatchSnapshot();
- }
-
- [Fact]
- public async Task Pure_Nullable_Scalar_Returns_Null_Should_Null_Without_Error()
- {
- var result = await new ServiceCollection()
- .AddGraphQL()
- .ModifyOptions(o => o.EnableSemanticNonNull = true)
- .AddQueryType()
- .ExecuteRequestAsync("""
- {
- pureNullableScalarReturningNull
- }
- """);
-
- result.MatchSnapshot();
- }
-
- #endregion
-
- #region Scalar List
-
- [Fact]
- public async Task Async_Scalar_List_Returns_Null_Should_Error()
- {
- var result = await new ServiceCollection()
- .AddGraphQL()
- .ModifyOptions(o => o.EnableSemanticNonNull = true)
- .AddQueryType()
- .ExecuteRequestAsync("""
- {
- scalarListReturningNull
- }
- """);
-
- result.MatchSnapshot();
- }
-
- [Fact]
- public async Task Async_Scalar_List_Throwing_Should_Null_And_Error()
- {
- var result = await new ServiceCollection()
- .AddGraphQL()
- .ModifyOptions(o => o.EnableSemanticNonNull = true)
- .AddQueryType()
- .ExecuteRequestAsync("""
- {
- scalarListThrowingError
- }
- """);
-
- result.MatchSnapshot();
- }
-
- [Fact]
- public async Task Async_Nullable_Scalar_List_Returns_Null_Should_Null_Without_Error()
- {
- var result = await new ServiceCollection()
- .AddGraphQL()
- .ModifyOptions(o => o.EnableSemanticNonNull = true)
- .AddQueryType()
- .ExecuteRequestAsync("""
- {
- nullableScalarListReturningNull
- }
- """);
-
- result.MatchSnapshot();
- }
-
- [Fact]
- public async Task Pure_Scalar_List_Returns_Null_Should_Error()
- {
- var result = await new ServiceCollection()
- .AddGraphQL()
- .ModifyOptions(o => o.EnableSemanticNonNull = true)
- .AddQueryType()
- .ExecuteRequestAsync("""
- {
- pureScalarListReturningNull
- }
- """);
-
- result.MatchSnapshot();
- }
-
- [Fact]
- public async Task Pure_Scalar_List_Throwing_Should_Null_And_Error()
- {
- var result = await new ServiceCollection()
- .AddGraphQL()
- .ModifyOptions(o => o.EnableSemanticNonNull = true)
- .AddQueryType()
- .ExecuteRequestAsync("""
- {
- pureScalarListThrowingError
- }
- """);
-
- result.MatchSnapshot();
- }
-
- [Fact]
- public async Task Pure_Nullable_Scalar_List_Returns_Null_Should_Null_Without_Error()
- {
- var result = await new ServiceCollection()
- .AddGraphQL()
- .ModifyOptions(o => o.EnableSemanticNonNull = true)
- .AddQueryType()
- .ExecuteRequestAsync("""
- {
- pureNullableScalarListReturningNull
- }
- """);
-
- result.MatchSnapshot();
- }
-
- #endregion
-
- #region Scalar List Item
-
- [Fact]
- public async Task Async_Scalar_List_Item_Returns_Null_Should_Error_Item()
- {
- var result = await new ServiceCollection()
- .AddGraphQL()
- .ModifyOptions(o => o.EnableSemanticNonNull = true)
- .AddQueryType()
- .ExecuteRequestAsync("""
- {
- scalarListItemReturningNull
- }
- """);
-
- result.MatchSnapshot();
- }
-
- [Fact]
- public async Task Async_Scalar_List_Item_Throwing_Should_Null_And_Error_Item()
- {
- var result = await new ServiceCollection()
- .AddGraphQL()
- .ModifyOptions(o => o.EnableSemanticNonNull = true)
- .AddQueryType()
- .ExecuteRequestAsync("""
- {
- scalarListItemThrowingError
- }
- """);
-
- result.MatchSnapshot();
- }
-
- [Fact]
- public async Task Async_Nullable_Scalar_List_Item_Returns_Null_Should_Null_Item_Without_Error()
- {
- var result = await new ServiceCollection()
- .AddGraphQL()
- .ModifyOptions(o => o.EnableSemanticNonNull = true)
- .AddQueryType()
- .ExecuteRequestAsync("""
- {
- nullableScalarListItemReturningNull
- }
- """);
-
- result.MatchSnapshot();
- }
-
- [Fact]
- public async Task Pure_Scalar_List_Item_Returns_Null_Should_Error_Item()
- {
- var result = await new ServiceCollection()
- .AddGraphQL()
- .ModifyOptions(o => o.EnableSemanticNonNull = true)
- .AddQueryType()
- .ExecuteRequestAsync("""
- {
- pureScalarListItemReturningNull
- }
- """);
-
- result.MatchSnapshot();
- }
-
- [Fact]
- public async Task Pure_Scalar_List_Item_Throwing_Should_Null_And_Error_Item()
- {
- var result = await new ServiceCollection()
- .AddGraphQL()
- .ModifyOptions(o => o.EnableSemanticNonNull = true)
- .AddQueryType()
- .ExecuteRequestAsync("""
- {
- pureScalarListItemThrowingError
- }
- """);
-
- result.MatchSnapshot();
- }
-
- [Fact]
- public async Task Pure_Nullable_Scalar_List_Item_Returns_Null_Should_Null_Item_Without_Error()
- {
- var result = await new ServiceCollection()
- .AddGraphQL()
- .ModifyOptions(o => o.EnableSemanticNonNull = true)
- .AddQueryType()
- .ExecuteRequestAsync("""
- {
- pureNullableScalarListItemReturningNull
- }
- """);
-
- result.MatchSnapshot();
- }
-
- #endregion
-
- #region Object
-
- [Fact]
- public async Task Async_Object_Returns_Null_Should_Error()
- {
- var result = await new ServiceCollection()
- .AddGraphQL()
- .ModifyOptions(o => o.EnableSemanticNonNull = true)
- .AddQueryType()
- .ExecuteRequestAsync("""
- {
- objectReturningNull {
- property
- }
- }
- """);
-
- result.MatchSnapshot();
- }
-
- [Fact]
- public async Task Async_Object_Throwing_Should_Null_And_Error()
- {
- var result = await new ServiceCollection()
- .AddGraphQL()
- .ModifyOptions(o => o.EnableSemanticNonNull = true)
- .AddQueryType()
- .ExecuteRequestAsync("""
- {
- objectThrowingError {
- property
- }
- }
- """);
-
- result.MatchSnapshot();
- }
-
- [Fact]
- public async Task Async_Nullable_Object_Returns_Null_Should_Null_Without_Error()
- {
- var result = await new ServiceCollection()
- .AddGraphQL()
- .ModifyOptions(o => o.EnableSemanticNonNull = true)
- .AddQueryType()
- .ExecuteRequestAsync("""
- {
- nullableObjectReturningNull {
- property
- }
- }
- """);
-
- result.MatchSnapshot();
- }
-
- [Fact]
- public async Task Pure_Object_Returns_Null_Should_Error()
- {
- var result = await new ServiceCollection()
- .AddGraphQL()
- .ModifyOptions(o => o.EnableSemanticNonNull = true)
- .AddQueryType()
- .ExecuteRequestAsync("""
- {
- pureObjectReturningNull {
- property
- }
- }
- """);
-
- result.MatchSnapshot();
- }
-
- [Fact]
- public async Task Pure_Object_Throwing_Should_Null_And_Error()
- {
- var result = await new ServiceCollection()
- .AddGraphQL()
- .ModifyOptions(o => o.EnableSemanticNonNull = true)
- .AddQueryType()
- .ExecuteRequestAsync("""
- {
- pureObjectThrowingError {
- property
- }
- }
- """);
-
- result.MatchSnapshot();
- }
-
- [Fact]
- public async Task Pure_Nullable_Object_Returns_Null_Should_Null_Without_Error()
- {
- var result = await new ServiceCollection()
- .AddGraphQL()
- .ModifyOptions(o => o.EnableSemanticNonNull = true)
- .AddQueryType()
- .ExecuteRequestAsync("""
- {
- pureNullableObjectReturningNull {
- property
- }
- }
- """);
-
- result.MatchSnapshot();
- }
-
- #endregion
-
- #region Object List
-
- [Fact]
- public async Task Async_Object_List_Returns_Null_Should_Error()
- {
- var result = await new ServiceCollection()
- .AddGraphQL()
- .ModifyOptions(o => o.EnableSemanticNonNull = true)
- .AddQueryType()
- .ExecuteRequestAsync("""
- {
- objectListReturningNull {
- property
- }
- }
- """);
-
- result.MatchSnapshot();
- }
-
- [Fact]
- public async Task Async_Object_List_Throwing_Should_Null_FAnd_Error()
- {
- var result = await new ServiceCollection()
- .AddGraphQL()
- .ModifyOptions(o => o.EnableSemanticNonNull = true)
- .AddQueryType()
- .ExecuteRequestAsync("""
- {
- objectListThrowingError {
- property
- }
- }
- """);
-
- result.MatchSnapshot();
- }
-
- [Fact]
- public async Task Async_Nullable_Object_List_Returns_Null_Should_Null_Without_Error()
- {
- var result = await new ServiceCollection()
- .AddGraphQL()
- .ModifyOptions(o => o.EnableSemanticNonNull = true)
- .AddQueryType()
- .ExecuteRequestAsync("""
- {
- nullableObjectListReturningNull {
- property
- }
- }
- """);
-
- result.MatchSnapshot();
- }
-
- [Fact]
- public async Task Pure_Object_List_Returns_Null_Should_Error()
- {
- var result = await new ServiceCollection()
- .AddGraphQL()
- .ModifyOptions(o => o.EnableSemanticNonNull = true)
- .AddQueryType()
- .ExecuteRequestAsync("""
- {
- pureObjectListReturningNull {
- property
- }
- }
- """);
-
- result.MatchSnapshot();
- }
-
- [Fact]
- public async Task Pure_Object_List_Throwing_Should_Null_FAnd_Error()
- {
- var result = await new ServiceCollection()
- .AddGraphQL()
- .ModifyOptions(o => o.EnableSemanticNonNull = true)
- .AddQueryType()
- .ExecuteRequestAsync("""
- {
- pureObjectListThrowingError {
- property
- }
- }
- """);
-
- result.MatchSnapshot();
- }
-
- [Fact]
- public async Task Pure_Nullable_Object_List_Returns_Null_Should_Null_Without_Error()
- {
- var result = await new ServiceCollection()
- .AddGraphQL()
- .ModifyOptions(o => o.EnableSemanticNonNull = true)
- .AddQueryType()
- .ExecuteRequestAsync("""
- {
- pureNullableObjectListReturningNull {
- property
- }
- }
- """);
-
- result.MatchSnapshot();
- }
-
- #endregion
-
- #region Object List Item
-
- [Fact]
- public async Task Async_Object_List_Item_Returns_Null_Should_Error_Item()
- {
- var result = await new ServiceCollection()
- .AddGraphQL()
- .ModifyOptions(o => o.EnableSemanticNonNull = true)
- .AddQueryType()
- .ExecuteRequestAsync("""
- {
- objectListItemReturningNull {
- property
- }
- }
- """);
-
- result.MatchSnapshot();
- }
-
- [Fact]
- public async Task Async_Object_List_Item_Throwing_Should_Null_And_Error_Item()
- {
- var result = await new ServiceCollection()
- .AddGraphQL()
- .ModifyOptions(o => o.EnableSemanticNonNull = true)
- .AddQueryType()
- .ExecuteRequestAsync("""
- {
- objectListItemThrowingError {
- property
- }
- }
- """);
-
- result.MatchSnapshot();
- }
-
- [Fact]
- public async Task Async_Nullable_Object_List_Item_Returns_Null_Should_Null_Item_Without_Error()
- {
- var result = await new ServiceCollection()
- .AddGraphQL()
- .ModifyOptions(o => o.EnableSemanticNonNull = true)
- .AddQueryType()
- .ExecuteRequestAsync("""
- {
- nullableObjectListItemReturningNull {
- property
- }
- }
- """);
-
- result.MatchSnapshot();
- }
-
- [Fact]
- public async Task Pure_Object_List_Item_Returns_Null_Should_Error_Item()
- {
- var result = await new ServiceCollection()
- .AddGraphQL()
- .ModifyOptions(o => o.EnableSemanticNonNull = true)
- .AddQueryType()
- .ExecuteRequestAsync("""
- {
- pureObjectListItemReturningNull {
- property
- }
- }
- """);
-
- result.MatchSnapshot();
- }
-
- [Fact]
- public async Task Pure_Object_List_Item_Throwing_Should_Null_And_Error_Item()
- {
- var result = await new ServiceCollection()
- .AddGraphQL()
- .ModifyOptions(o => o.EnableSemanticNonNull = true)
- .AddQueryType()
- .ExecuteRequestAsync("""
- {
- pureObjectListItemThrowingError {
- property
- }
- }
- """);
-
- result.MatchSnapshot();
- }
-
- [Fact]
- public async Task Pure_Nullable_Object_List_Item_Returns_Null_Should_Null_Item_Without_Error()
- {
- var result = await new ServiceCollection()
- .AddGraphQL()
- .ModifyOptions(o => o.EnableSemanticNonNull = true)
- .AddQueryType()
- .ExecuteRequestAsync("""
- {
- pureNullableObjectListItemReturningNull {
- property
- }
- }
- """);
-
- result.MatchSnapshot();
- }
-
- #endregion
-
- [Fact]
- public async Task Mutation_With_MutationConventions()
- {
- var result = await new ServiceCollection()
- .AddGraphQL()
- .ModifyOptions(o =>
- {
- o.StrictValidation = false;
- o.EnableSemanticNonNull = true;
- })
- .AddMutationConventions()
- .AddMutationType()
- .ExecuteRequestAsync("""
- mutation {
- someMutationReturningNull {
- scalarReturningNull
- }
- }
- """);
-
- result.MatchSnapshot();
- }
-
- [Fact]
- public async Task Query_With_Connection()
- {
- var result = await new ServiceCollection()
- .AddGraphQL()
- .ModifyOptions(o => o.EnableSemanticNonNull = true)
- .AddMutationConventions()
- .AddQueryType()
- .ExecuteRequestAsync("""
- {
- scalarConnection {
- edges {
- node
- }
- }
- }
- """);
-
- result.MatchSnapshot();
- }
-
- [Fact]
- public async Task Query_With_NullableConnectionNodes()
- {
- var result = await new ServiceCollection()
- .AddGraphQL()
- .ModifyOptions(o => o.EnableSemanticNonNull = true)
- .AddMutationConventions()
- .AddQueryType()
- .ExecuteRequestAsync("""
- {
- nullableScalarConnection {
- edges {
- node
- }
- }
- }
- """);
-
- result.MatchSnapshot();
- }
-
- [Fact]
- public async Task Pure_Scalar_ListOfList_Nullable_Outer_And_Inner_Middle_Returns_Null_Should_Null_And_Error()
- {
- var result = await new ServiceCollection()
- .AddGraphQL()
- .ModifyOptions(o => o.EnableSemanticNonNull = true)
- .AddMutationConventions()
- .AddQueryType()
- .ExecuteRequestAsync("""
- {
- nestedScalarArrayNullableOuterItems
- }
- """);
-
- result.MatchSnapshot();
- }
-
- [Fact]
- public async Task Pure_Scalar_ListOfList_Nullable_Middle_Item_Outer_And_Inner_Return_Null_Should_Null_And_Error()
- {
- var result = await new ServiceCollection()
- .AddGraphQL()
- .ModifyOptions(o => o.EnableSemanticNonNull = true)
- .AddMutationConventions()
- .AddQueryType()
- .ExecuteRequestAsync("""
- {
- nestedScalarArrayNullableMiddleItem
- }
- """);
-
- result.MatchSnapshot();
- }
-
- public class Query
- {
- #region Scalar
-
- public Task ScalarReturningNull()
- {
- return Task.FromResult(null!);
- }
-
- public Task ScalarThrowingError()
- {
- throw new Exception("Something went wrong");
- }
-
- public Task NullableScalarReturningNull()
- {
- return Task.FromResult(null);
- }
-
- public string PureScalarReturningNull => null!;
-
- public string PureScalarThrowingError => throw new Exception("Somethin went wrong");
-
- public string? PureNullableScalarReturningNull => null;
-
- #endregion
-
- #region Scalar List
-
- public Task ScalarListReturningNull()
- {
- return Task.FromResult(null!);
- }
-
- public Task ScalarListThrowingError()
- {
- throw new Exception("Something went wrong");
- }
-
- public Task NullableScalarListReturningNull()
- {
- return Task.FromResult(null);
- }
-
- public string[] PureScalarListReturningNull => null!;
-
- public string[] PureScalarListThrowingError => throw new Exception("Somethin went wrong");
-
- public string[]? PureNullableScalarListReturningNull => null;
-
- #endregion
-
- #region Scalar List Item
-
- public Task ScalarListItemReturningNull()
- {
- return Task.FromResult(["a", null!, "c"]);
- }
-
- public Task ScalarListItemThrowingError(IResolverContext context)
- {
- // TODO: How can you create a terminating error for a single item?
- context.ReportError(ErrorBuilder.New().SetMessage("Another error").SetPath(context.Path.Append(1)).Build());
- return Task.FromResult(["a", null!, "c"]);
- }
-
- public Task NullableScalarListItemReturningNull()
- {
- return Task.FromResult(["a", null, "c"]);
- }
-
- public string[] PureScalarListItemReturningNull => ["a", null!, "c"];
-
- // TODO: This is no longer a pure resolver as soon as it access the IResolverContext, right?
- public string[] PureScalarListItemThrowingError(IResolverContext context)
- {
- // TODO: How can you create a terminating error for a single item?
- context.ReportError(ErrorBuilder.New().SetMessage("Another error").SetPath(context.Path.Append(1)).Build());
- return ["a", null!, "c"];
- }
-
- public string?[] PureNullableScalarListItemReturningNull => ["a", null, "c"];
-
- #endregion
-
- #region Object
-
- public Task ObjectReturningNull()
- {
- return Task.FromResult(null!);
- }
-
- public Task ObjectThrowingError()
- {
- throw new Exception("Something went wrong");
- }
-
- public Task NullableObjectReturningNull()
- {
- return Task.FromResult(null);
- }
-
- public SomeObject PureObjectReturningNull => null!;
-
- public SomeObject PureObjectThrowingError => throw new Exception("Somethin went wrong");
-
- public SomeObject? PureNullableObjectReturningNull => null;
-
- #endregion
-
- #region Object List
-
- public Task ObjectListReturningNull()
- {
- return Task.FromResult(null!);
- }
-
- public Task ObjectListThrowingError()
- {
- throw new Exception("Something went wrong");
- }
-
- public Task NullableObjectListReturningNull()
- {
- return Task.FromResult(null);
- }
-
- public SomeObject[] PureObjectListReturningNull => null!;
-
- public SomeObject[] PureObjectListThrowingError => throw new Exception("Somethin went wrong");
-
- public SomeObject[]? PureNullableObjectListReturningNull => null;
-
- #endregion
-
- #region Object List Item
-
- public Task ObjectListItemReturningNull()
- {
- return Task.FromResult([new("a"), null!, new("c")]);
- }
-
- public Task ObjectListItemThrowingError(IResolverContext context)
- {
- context.ReportError(ErrorBuilder.New().SetMessage("Another error").SetPath(context.Path.Append(1)).Build());
- return Task.FromResult([new("a"), null!, new("c")]);
- }
-
- public Task NullableObjectListItemReturningNull()
- {
- return Task.FromResult([new("a"), null, new("c")]);
- }
-
- public SomeObject[] PureObjectListItemReturningNull => [new("a"), null!, new("c")];
-
- // TODO: This is no longer a pure resolver as soon as it access the IResolverContext, right?
- public SomeObject[] PureObjectListItemThrowingError(IResolverContext context)
- {
- context.ReportError(ErrorBuilder.New().SetMessage("Another error").SetPath(context.Path.Append(1)).Build());
- return [new("a"), null!, new("c")];
- }
-
- public SomeObject?[] PureNullableObjectListItemReturningNull => [new("a"), null, new("c")];
-
- #endregion
-
- #region Nested Array
- public string?[][]? NestedScalarArrayNullableOuterItems()
- {
- return [["a1", null!, "c1"], null!, ["a2", null!, "c2"]];
- }
-
- public string[]?[] NestedScalarArrayNullableMiddleItem()
- {
- return [["a1", null!, "c1"], null!, ["a2", null!, "c2"]];
- }
- #endregion
-
- [UsePaging]
- public string[] ScalarConnection() => ["a", null!, "c"];
-
- [UsePaging]
- public string?[] NullableScalarConnection() => ["a", null, "c"];
- }
-
- public record SomeObject(string Property);
-
- public class Mutation
- {
- [UseMutationConvention(PayloadFieldName = "scalarReturningNull")]
- public Task SomeMutationReturningNull() => Task.FromResult(null!);
- }
-}
diff --git a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Nullable_Object_List_Item_Returns_Null_Should_Null_Item_Without_Error.snap b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Nullable_Object_List_Item_Returns_Null_Should_Null_Item_Without_Error.snap
deleted file mode 100644
index 5bf14273af3..00000000000
--- a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Nullable_Object_List_Item_Returns_Null_Should_Null_Item_Without_Error.snap
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "data": {
- "nullableObjectListItemReturningNull": [
- {
- "property": "a"
- },
- null,
- {
- "property": "c"
- }
- ]
- }
-}
diff --git a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Nullable_Object_List_Returns_Null_Should_Null_Without_Error.snap b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Nullable_Object_List_Returns_Null_Should_Null_Without_Error.snap
deleted file mode 100644
index 80512055ffc..00000000000
--- a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Nullable_Object_List_Returns_Null_Should_Null_Without_Error.snap
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "data": {
- "nullableObjectListReturningNull": null
- }
-}
diff --git a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Nullable_Object_Returns_Null_Should_Null_Without_Error.snap b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Nullable_Object_Returns_Null_Should_Null_Without_Error.snap
deleted file mode 100644
index d6ed49ca517..00000000000
--- a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Nullable_Object_Returns_Null_Should_Null_Without_Error.snap
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "data": {
- "nullableObjectReturningNull": null
- }
-}
diff --git a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Nullable_Scalar_List_Item_Returns_Null_Should_Null_Item_Without_Error.snap b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Nullable_Scalar_List_Item_Returns_Null_Should_Null_Item_Without_Error.snap
deleted file mode 100644
index b3c51911a4c..00000000000
--- a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Nullable_Scalar_List_Item_Returns_Null_Should_Null_Item_Without_Error.snap
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "data": {
- "nullableScalarListItemReturningNull": [
- "a",
- null,
- "c"
- ]
- }
-}
diff --git a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Nullable_Scalar_List_Returns_Null_Should_Null_Without_Error.snap b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Nullable_Scalar_List_Returns_Null_Should_Null_Without_Error.snap
deleted file mode 100644
index 68f0bbd3b50..00000000000
--- a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Nullable_Scalar_List_Returns_Null_Should_Null_Without_Error.snap
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "data": {
- "nullableScalarListReturningNull": null
- }
-}
diff --git a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Nullable_Scalar_Returns_Null_Should_Null_Without_Error.snap b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Nullable_Scalar_Returns_Null_Should_Null_Without_Error.snap
deleted file mode 100644
index 161eb3e36df..00000000000
--- a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Nullable_Scalar_Returns_Null_Should_Null_Without_Error.snap
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "data": {
- "nullableScalarReturningNull": null
- }
-}
diff --git a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Object_List_Item_Returns_Null_Should_Error_Item.snap b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Object_List_Item_Returns_Null_Should_Error_Item.snap
deleted file mode 100644
index e64c9d4a214..00000000000
--- a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Object_List_Item_Returns_Null_Should_Error_Item.snap
+++ /dev/null
@@ -1,25 +0,0 @@
-{
- "errors": [
- {
- "message": "Cannot return null for semantic non-null field.",
- "path": [
- "objectListItemReturningNull",
- 1
- ],
- "extensions": {
- "code": "HC0088"
- }
- }
- ],
- "data": {
- "objectListItemReturningNull": [
- {
- "property": "a"
- },
- null,
- {
- "property": "c"
- }
- ]
- }
-}
diff --git a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Object_List_Item_Throwing_Should_Null_And_Error_Item.snap b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Object_List_Item_Throwing_Should_Null_And_Error_Item.snap
deleted file mode 100644
index d578768ba80..00000000000
--- a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Object_List_Item_Throwing_Should_Null_And_Error_Item.snap
+++ /dev/null
@@ -1,32 +0,0 @@
-{
- "errors": [
- {
- "message": "Another error",
- "path": [
- "objectListItemThrowingError",
- 1
- ]
- },
- {
- "message": "Cannot return null for semantic non-null field.",
- "path": [
- "objectListItemThrowingError",
- 1
- ],
- "extensions": {
- "code": "HC0088"
- }
- }
- ],
- "data": {
- "objectListItemThrowingError": [
- {
- "property": "a"
- },
- null,
- {
- "property": "c"
- }
- ]
- }
-}
diff --git a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Object_List_Returns_Null_Should_Error.snap b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Object_List_Returns_Null_Should_Error.snap
deleted file mode 100644
index cd74246f7cc..00000000000
--- a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Object_List_Returns_Null_Should_Error.snap
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "errors": [
- {
- "message": "Cannot return null for semantic non-null field.",
- "path": [
- "objectListReturningNull"
- ],
- "extensions": {
- "code": "HC0088"
- }
- }
- ],
- "data": {
- "objectListReturningNull": null
- }
-}
diff --git a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Object_List_Throwing_Should_Null_FAnd_Error.snap b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Object_List_Throwing_Should_Null_FAnd_Error.snap
deleted file mode 100644
index 1a8e1b1447f..00000000000
--- a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Object_List_Throwing_Should_Null_FAnd_Error.snap
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "errors": [
- {
- "message": "Unexpected Execution Error",
- "path": [
- "objectListThrowingError"
- ]
- }
- ],
- "data": {
- "objectListThrowingError": null
- }
-}
diff --git a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Object_Returns_Null_Should_Error.snap b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Object_Returns_Null_Should_Error.snap
deleted file mode 100644
index de90deed3b8..00000000000
--- a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Object_Returns_Null_Should_Error.snap
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "errors": [
- {
- "message": "Cannot return null for semantic non-null field.",
- "path": [
- "objectReturningNull"
- ],
- "extensions": {
- "code": "HC0088"
- }
- }
- ],
- "data": {
- "objectReturningNull": null
- }
-}
diff --git a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Object_Throwing_Should_Null_And_Error.snap b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Object_Throwing_Should_Null_And_Error.snap
deleted file mode 100644
index 57ccb939ca0..00000000000
--- a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Object_Throwing_Should_Null_And_Error.snap
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "errors": [
- {
- "message": "Unexpected Execution Error",
- "path": [
- "objectThrowingError"
- ]
- }
- ],
- "data": {
- "objectThrowingError": null
- }
-}
diff --git a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Scalar_List_Item_Returns_Null_Should_Error_Item.snap b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Scalar_List_Item_Returns_Null_Should_Error_Item.snap
deleted file mode 100644
index f4a6d5a8478..00000000000
--- a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Scalar_List_Item_Returns_Null_Should_Error_Item.snap
+++ /dev/null
@@ -1,21 +0,0 @@
-{
- "errors": [
- {
- "message": "Cannot return null for semantic non-null field.",
- "path": [
- "scalarListItemReturningNull",
- 1
- ],
- "extensions": {
- "code": "HC0088"
- }
- }
- ],
- "data": {
- "scalarListItemReturningNull": [
- "a",
- null,
- "c"
- ]
- }
-}
diff --git a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Scalar_List_Item_Throwing_Should_Null_And_Error_Item.snap b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Scalar_List_Item_Throwing_Should_Null_And_Error_Item.snap
deleted file mode 100644
index 464d5cdb295..00000000000
--- a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Scalar_List_Item_Throwing_Should_Null_And_Error_Item.snap
+++ /dev/null
@@ -1,28 +0,0 @@
-{
- "errors": [
- {
- "message": "Another error",
- "path": [
- "scalarListItemThrowingError",
- 1
- ]
- },
- {
- "message": "Cannot return null for semantic non-null field.",
- "path": [
- "scalarListItemThrowingError",
- 1
- ],
- "extensions": {
- "code": "HC0088"
- }
- }
- ],
- "data": {
- "scalarListItemThrowingError": [
- "a",
- null,
- "c"
- ]
- }
-}
diff --git a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Scalar_List_Returns_Null_Should_Error.snap b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Scalar_List_Returns_Null_Should_Error.snap
deleted file mode 100644
index 7c3567e8c3f..00000000000
--- a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Scalar_List_Returns_Null_Should_Error.snap
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "errors": [
- {
- "message": "Cannot return null for semantic non-null field.",
- "path": [
- "scalarListReturningNull"
- ],
- "extensions": {
- "code": "HC0088"
- }
- }
- ],
- "data": {
- "scalarListReturningNull": null
- }
-}
diff --git a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Scalar_List_Throwing_Should_Null_And_Error.snap b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Scalar_List_Throwing_Should_Null_And_Error.snap
deleted file mode 100644
index a406ecdc3d9..00000000000
--- a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Scalar_List_Throwing_Should_Null_And_Error.snap
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "errors": [
- {
- "message": "Unexpected Execution Error",
- "path": [
- "scalarListThrowingError"
- ]
- }
- ],
- "data": {
- "scalarListThrowingError": null
- }
-}
diff --git a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Scalar_Returns_Null_Should_Error.snap b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Scalar_Returns_Null_Should_Error.snap
deleted file mode 100644
index d12cef73fdc..00000000000
--- a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Scalar_Returns_Null_Should_Error.snap
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "errors": [
- {
- "message": "Cannot return null for semantic non-null field.",
- "path": [
- "scalarReturningNull"
- ],
- "extensions": {
- "code": "HC0088"
- }
- }
- ],
- "data": {
- "scalarReturningNull": null
- }
-}
diff --git a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Scalar_Throwing_Should_Null_And_Error.snap b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Scalar_Throwing_Should_Null_And_Error.snap
deleted file mode 100644
index ced147f1349..00000000000
--- a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Scalar_Throwing_Should_Null_And_Error.snap
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "errors": [
- {
- "message": "Unexpected Execution Error",
- "path": [
- "scalarThrowingError"
- ]
- }
- ],
- "data": {
- "scalarThrowingError": null
- }
-}
diff --git a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Mutation_With_MutationConventions.snap b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Mutation_With_MutationConventions.snap
deleted file mode 100644
index bd13441af00..00000000000
--- a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Mutation_With_MutationConventions.snap
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "data": {
- "someMutationReturningNull": {
- "scalarReturningNull": null
- }
- }
-}
diff --git a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Nullable_Object_List_Item_Returns_Null_Should_Null_Item_Without_Error.snap b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Nullable_Object_List_Item_Returns_Null_Should_Null_Item_Without_Error.snap
deleted file mode 100644
index bc867086e8a..00000000000
--- a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Nullable_Object_List_Item_Returns_Null_Should_Null_Item_Without_Error.snap
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "data": {
- "pureNullableObjectListItemReturningNull": [
- {
- "property": "a"
- },
- null,
- {
- "property": "c"
- }
- ]
- }
-}
diff --git a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Nullable_Object_List_Returns_Null_Should_Null_Without_Error.snap b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Nullable_Object_List_Returns_Null_Should_Null_Without_Error.snap
deleted file mode 100644
index 2abd30023ac..00000000000
--- a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Nullable_Object_List_Returns_Null_Should_Null_Without_Error.snap
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "data": {
- "pureNullableObjectListReturningNull": null
- }
-}
diff --git a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Nullable_Object_Returns_Null_Should_Null_Without_Error.snap b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Nullable_Object_Returns_Null_Should_Null_Without_Error.snap
deleted file mode 100644
index 472da5015a4..00000000000
--- a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Nullable_Object_Returns_Null_Should_Null_Without_Error.snap
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "data": {
- "pureNullableObjectReturningNull": null
- }
-}
diff --git a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Nullable_Scalar_List_Item_Returns_Null_Should_Null_Item_Without_Error.snap b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Nullable_Scalar_List_Item_Returns_Null_Should_Null_Item_Without_Error.snap
deleted file mode 100644
index 65e0c7452b1..00000000000
--- a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Nullable_Scalar_List_Item_Returns_Null_Should_Null_Item_Without_Error.snap
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "data": {
- "pureNullableScalarListItemReturningNull": [
- "a",
- null,
- "c"
- ]
- }
-}
diff --git a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Nullable_Scalar_List_Returns_Null_Should_Null_Without_Error.snap b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Nullable_Scalar_List_Returns_Null_Should_Null_Without_Error.snap
deleted file mode 100644
index 859a7fd908e..00000000000
--- a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Nullable_Scalar_List_Returns_Null_Should_Null_Without_Error.snap
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "data": {
- "pureNullableScalarListReturningNull": null
- }
-}
diff --git a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Nullable_Scalar_Returns_Null_Should_Null_Without_Error.snap b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Nullable_Scalar_Returns_Null_Should_Null_Without_Error.snap
deleted file mode 100644
index e05006cc33f..00000000000
--- a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Nullable_Scalar_Returns_Null_Should_Null_Without_Error.snap
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "data": {
- "pureNullableScalarReturningNull": null
- }
-}
diff --git a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Object_List_Item_Returns_Null_Should_Error_Item.snap b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Object_List_Item_Returns_Null_Should_Error_Item.snap
deleted file mode 100644
index 702c8496c8a..00000000000
--- a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Object_List_Item_Returns_Null_Should_Error_Item.snap
+++ /dev/null
@@ -1,25 +0,0 @@
-{
- "errors": [
- {
- "message": "Cannot return null for semantic non-null field.",
- "path": [
- "pureObjectListItemReturningNull",
- 1
- ],
- "extensions": {
- "code": "HC0088"
- }
- }
- ],
- "data": {
- "pureObjectListItemReturningNull": [
- {
- "property": "a"
- },
- null,
- {
- "property": "c"
- }
- ]
- }
-}
diff --git a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Object_List_Item_Throwing_Should_Null_And_Error_Item.snap b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Object_List_Item_Throwing_Should_Null_And_Error_Item.snap
deleted file mode 100644
index 561c8238624..00000000000
--- a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Object_List_Item_Throwing_Should_Null_And_Error_Item.snap
+++ /dev/null
@@ -1,32 +0,0 @@
-{
- "errors": [
- {
- "message": "Another error",
- "path": [
- "pureObjectListItemThrowingError",
- 1
- ]
- },
- {
- "message": "Cannot return null for semantic non-null field.",
- "path": [
- "pureObjectListItemThrowingError",
- 1
- ],
- "extensions": {
- "code": "HC0088"
- }
- }
- ],
- "data": {
- "pureObjectListItemThrowingError": [
- {
- "property": "a"
- },
- null,
- {
- "property": "c"
- }
- ]
- }
-}
diff --git a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Object_List_Returns_Null_Should_Error.snap b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Object_List_Returns_Null_Should_Error.snap
deleted file mode 100644
index 2262faed20a..00000000000
--- a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Object_List_Returns_Null_Should_Error.snap
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "errors": [
- {
- "message": "Cannot return null for semantic non-null field.",
- "path": [
- "pureObjectListReturningNull"
- ],
- "extensions": {
- "code": "HC0088"
- }
- }
- ],
- "data": {
- "pureObjectListReturningNull": null
- }
-}
diff --git a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Object_List_Throwing_Should_Null_FAnd_Error.snap b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Object_List_Throwing_Should_Null_FAnd_Error.snap
deleted file mode 100644
index 42516bd3c4b..00000000000
--- a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Object_List_Throwing_Should_Null_FAnd_Error.snap
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "errors": [
- {
- "message": "Unexpected Execution Error",
- "path": [
- "pureObjectListThrowingError"
- ]
- }
- ],
- "data": {
- "pureObjectListThrowingError": null
- }
-}
diff --git a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Object_Returns_Null_Should_Error.snap b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Object_Returns_Null_Should_Error.snap
deleted file mode 100644
index c15611a44a1..00000000000
--- a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Object_Returns_Null_Should_Error.snap
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "errors": [
- {
- "message": "Cannot return null for semantic non-null field.",
- "path": [
- "pureObjectReturningNull"
- ],
- "extensions": {
- "code": "HC0088"
- }
- }
- ],
- "data": {
- "pureObjectReturningNull": null
- }
-}
diff --git a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Object_Throwing_Should_Null_And_Error.snap b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Object_Throwing_Should_Null_And_Error.snap
deleted file mode 100644
index 63eeb3487ae..00000000000
--- a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Object_Throwing_Should_Null_And_Error.snap
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "errors": [
- {
- "message": "Unexpected Execution Error",
- "path": [
- "pureObjectThrowingError"
- ]
- }
- ],
- "data": {
- "pureObjectThrowingError": null
- }
-}
diff --git a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Scalar_ListOfList_Nullable_Middle_Item_Outer_And_Inner_Return_Null_Should_Null_And_Error.snap b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Scalar_ListOfList_Nullable_Middle_Item_Outer_And_Inner_Return_Null_Should_Null_And_Error.snap
deleted file mode 100644
index 7bf76b7e4c0..00000000000
--- a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Scalar_ListOfList_Nullable_Middle_Item_Outer_And_Inner_Return_Null_Should_Null_And_Error.snap
+++ /dev/null
@@ -1,41 +0,0 @@
-{
- "errors": [
- {
- "message": "Cannot return null for semantic non-null field.",
- "path": [
- "nestedScalarArrayNullableMiddleItem",
- 0,
- 1
- ],
- "extensions": {
- "code": "HC0088"
- }
- },
- {
- "message": "Cannot return null for semantic non-null field.",
- "path": [
- "nestedScalarArrayNullableMiddleItem",
- 2,
- 1
- ],
- "extensions": {
- "code": "HC0088"
- }
- }
- ],
- "data": {
- "nestedScalarArrayNullableMiddleItem": [
- [
- "a1",
- null,
- "c1"
- ],
- null,
- [
- "a2",
- null,
- "c2"
- ]
- ]
- }
-}
diff --git a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Scalar_ListOfList_Nullable_Outer_And_Inner_Middle_Returns_Null_Should_Null_And_Error.snap b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Scalar_ListOfList_Nullable_Outer_And_Inner_Middle_Returns_Null_Should_Null_And_Error.snap
deleted file mode 100644
index bf9a690b5f8..00000000000
--- a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Scalar_ListOfList_Nullable_Outer_And_Inner_Middle_Returns_Null_Should_Null_And_Error.snap
+++ /dev/null
@@ -1,29 +0,0 @@
-{
- "errors": [
- {
- "message": "Cannot return null for semantic non-null field.",
- "path": [
- "nestedScalarArrayNullableOuterItems",
- 1
- ],
- "extensions": {
- "code": "HC0088"
- }
- }
- ],
- "data": {
- "nestedScalarArrayNullableOuterItems": [
- [
- "a1",
- null,
- "c1"
- ],
- null,
- [
- "a2",
- null,
- "c2"
- ]
- ]
- }
-}
diff --git a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Scalar_List_Item_Returns_Null_Should_Error_Item.snap b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Scalar_List_Item_Returns_Null_Should_Error_Item.snap
deleted file mode 100644
index ed0a4dd9a70..00000000000
--- a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Scalar_List_Item_Returns_Null_Should_Error_Item.snap
+++ /dev/null
@@ -1,21 +0,0 @@
-{
- "errors": [
- {
- "message": "Cannot return null for semantic non-null field.",
- "path": [
- "pureScalarListItemReturningNull",
- 1
- ],
- "extensions": {
- "code": "HC0088"
- }
- }
- ],
- "data": {
- "pureScalarListItemReturningNull": [
- "a",
- null,
- "c"
- ]
- }
-}
diff --git a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Scalar_List_Item_Throwing_Should_Null_And_Error_Item.snap b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Scalar_List_Item_Throwing_Should_Null_And_Error_Item.snap
deleted file mode 100644
index cdb53b6f7ff..00000000000
--- a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Scalar_List_Item_Throwing_Should_Null_And_Error_Item.snap
+++ /dev/null
@@ -1,28 +0,0 @@
-{
- "errors": [
- {
- "message": "Another error",
- "path": [
- "pureScalarListItemThrowingError",
- 1
- ]
- },
- {
- "message": "Cannot return null for semantic non-null field.",
- "path": [
- "pureScalarListItemThrowingError",
- 1
- ],
- "extensions": {
- "code": "HC0088"
- }
- }
- ],
- "data": {
- "pureScalarListItemThrowingError": [
- "a",
- null,
- "c"
- ]
- }
-}
diff --git a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Scalar_List_Returns_Null_Should_Error.snap b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Scalar_List_Returns_Null_Should_Error.snap
deleted file mode 100644
index 465984d2f34..00000000000
--- a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Scalar_List_Returns_Null_Should_Error.snap
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "errors": [
- {
- "message": "Cannot return null for semantic non-null field.",
- "path": [
- "pureScalarListReturningNull"
- ],
- "extensions": {
- "code": "HC0088"
- }
- }
- ],
- "data": {
- "pureScalarListReturningNull": null
- }
-}
diff --git a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Scalar_List_Throwing_Should_Null_And_Error.snap b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Scalar_List_Throwing_Should_Null_And_Error.snap
deleted file mode 100644
index 44c06c8d747..00000000000
--- a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Scalar_List_Throwing_Should_Null_And_Error.snap
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "errors": [
- {
- "message": "Unexpected Execution Error",
- "path": [
- "pureScalarListThrowingError"
- ]
- }
- ],
- "data": {
- "pureScalarListThrowingError": null
- }
-}
diff --git a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Scalar_Returns_Null_Should_Error.snap b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Scalar_Returns_Null_Should_Error.snap
deleted file mode 100644
index d2e3122ede9..00000000000
--- a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Scalar_Returns_Null_Should_Error.snap
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "errors": [
- {
- "message": "Cannot return null for semantic non-null field.",
- "path": [
- "pureScalarReturningNull"
- ],
- "extensions": {
- "code": "HC0088"
- }
- }
- ],
- "data": {
- "pureScalarReturningNull": null
- }
-}
diff --git a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Scalar_Throwing_Should_Null_And_Error.snap b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Scalar_Throwing_Should_Null_And_Error.snap
deleted file mode 100644
index 1cbac92d059..00000000000
--- a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Scalar_Throwing_Should_Null_And_Error.snap
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "errors": [
- {
- "message": "Unexpected Execution Error",
- "path": [
- "pureScalarThrowingError"
- ]
- }
- ],
- "data": {
- "pureScalarThrowingError": null
- }
-}
diff --git a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Query_With_Connection.snap b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Query_With_Connection.snap
deleted file mode 100644
index e0e253ee4af..00000000000
--- a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Query_With_Connection.snap
+++ /dev/null
@@ -1,28 +0,0 @@
-{
- "errors": [
- {
- "message": "Unexpected Execution Error",
- "path": [
- "scalarConnection",
- "edges",
- 1,
- "node"
- ]
- }
- ],
- "data": {
- "scalarConnection": {
- "edges": [
- {
- "node": "a"
- },
- {
- "node": null
- },
- {
- "node": "c"
- }
- ]
- }
- }
-}
diff --git a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Query_With_NullableConnectionNodes.snap b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Query_With_NullableConnectionNodes.snap
deleted file mode 100644
index ba3ef427b57..00000000000
--- a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Query_With_NullableConnectionNodes.snap
+++ /dev/null
@@ -1,17 +0,0 @@
-{
- "data": {
- "nullableScalarConnection": {
- "edges": [
- {
- "node": "a"
- },
- {
- "node": null
- },
- {
- "node": "c"
- }
- ]
- }
- }
-}
diff --git a/src/HotChocolate/Core/test/Types.Tests/SemanticNonNullTests.cs b/src/HotChocolate/Core/test/Types.Tests/SemanticNonNullTests.cs
deleted file mode 100644
index 6fd56992283..00000000000
--- a/src/HotChocolate/Core/test/Types.Tests/SemanticNonNullTests.cs
+++ /dev/null
@@ -1,382 +0,0 @@
-using HotChocolate.Execution;
-using HotChocolate.Tests;
-using HotChocolate.Types;
-using HotChocolate.Types.Descriptors;
-using HotChocolate.Types.Relay;
-using Microsoft.Extensions.DependencyInjection;
-
-namespace HotChocolate;
-
-public class SemanticNonNullTests
-{
- [Fact]
- public async Task Object_With_Id_Field()
- {
- await new ServiceCollection()
- .AddGraphQL()
- .ModifyOptions(o => o.EnableSemanticNonNull = true)
- .AddQueryType()
- .BuildSchemaAsync()
- .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()
- {
- await new ServiceCollection()
- .AddGraphQL()
- .ModifyOptions(o =>
- {
- o.StrictValidation = false;
- o.EnableSemanticNonNull = true;
- })
- .AddMutationConventions(applyToAllMutations: false)
- .AddMutationType()
- .AddTypeExtension()
- .BuildSchemaAsync()
- .MatchSnapshotAsync();
- }
-
- [Fact]
- public async Task Pagination()
- {
- await new ServiceCollection()
- .AddGraphQL()
- .ModifyOptions(o => o.EnableSemanticNonNull = true)
- .AddQueryType()
- .BuildSchemaAsync()
- .MatchSnapshotAsync();
- }
-
- [Fact]
- public async Task Derive_SemanticNonNull_From_ImplementationFirst()
- {
- await new ServiceCollection()
- .AddGraphQL()
- .ModifyOptions(o => o.EnableSemanticNonNull = true)
- .AddQueryType()
- .BuildSchemaAsync()
- .MatchSnapshotAsync();
- }
-
- [Fact]
- public async Task Derive_SemanticNonNull_From_ImplementationFirst_With_GraphQLType_As_Type()
- {
- await new ServiceCollection()
- .AddGraphQL()
- .ModifyOptions(o => o.EnableSemanticNonNull = true)
- .AddQueryType()
- .BuildSchemaAsync()
- .MatchSnapshotAsync();
- }
-
- [Fact]
- public async Task Derive_SemanticNonNull_From_ImplementationFirst_With_GraphQLType_As_String()
- {
- await new ServiceCollection()
- .AddGraphQL()
- .ModifyOptions(o => o.EnableSemanticNonNull = true)
- .AddType()
- .AddQueryType()
- .BuildSchemaAsync()
- .MatchSnapshotAsync();
- }
-
- [Fact]
- public async Task Derive_SemanticNonNull_From_CodeFirst()
- {
- await new ServiceCollection()
- .AddGraphQL()
- .ModifyOptions(o => o.EnableSemanticNonNull = true)
- .AddQueryType()
- .UseField(_ => _ => default)
- .BuildSchemaAsync()
- .MatchSnapshotAsync();
- }
-
- [Fact]
- public async Task Apply_SemanticNonNull_To_SchemaFirst()
- {
- await new ServiceCollection()
- .AddGraphQL()
- .ModifyOptions(o => o.EnableSemanticNonNull = true)
- .AddDocumentFromString(
- """
- type Query {
- scalar: String
- nonNulScalar: String!
- scalarArray: [String]
- nonNullScalarArray: [String!]!
- outerNonNullScalarArray: [String]!
- scalarNestedArray: [[String]]
- nonNullScalarNestedArray: [[String!]!]!
- innerNonNullScalarNestedArray: [[String!]]!
- object: Foo
- nonNullObject: Foo!
- objectArray: [Foo]
- nonNullObjectArray: [Foo!]!
- objectNestedArray: [[Foo]]
- nonNullObjectNestedArray: [[Foo!]!]!
- innerNonNullObjectNestedArray: [[Foo!]]!
- }
-
- type Foo {
- bar: String!
- }
- """)
- .UseField(_ => _ => default)
- .BuildSchemaAsync()
- .MatchSnapshotAsync();
- }
-
- public class QueryWithInteface
- {
- public SomeObject GetSomeObject() => new();
- }
-
- [Node]
- [ImplementsInterface]
- public record SomeObject
- {
- public int Id { get; set; }
-
- public string Field { get; set; } = null!;
-
- 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)
- {
- descriptor.Name("Query");
- descriptor.Field("scalar").Type();
- descriptor.Field("nonNulScalar").Type>();
- descriptor.Field("scalarArray").Type>();
- descriptor.Field("nonNullScalarArray").Type>>>();
- descriptor.Field("outerNonNullScalarArray").Type>>();
- descriptor.Field("scalarNestedArray").Type>>();
- descriptor.Field("nonNullScalarNestedArray").Type>>>>>();
- descriptor.Field("innerNonNullScalarNestedArray").Type>>>>();
- descriptor.Field("object").Type();
- descriptor.Field("nonNullObject").Type>();
- descriptor.Field("objectArray").Type>();
- descriptor.Field("nonNullObjectArray").Type>>>();
- descriptor.Field("objectNestedArray").Type>>();
- descriptor.Field("nonNullObjectNestedArray").Type>>>>>();
- descriptor.Field("innerNonNullObjectNestedArray").Type>>>>();
- }
- }
-
- public class FooType : ObjectType
- {
- protected override void Configure(IObjectTypeDescriptor descriptor)
- {
- descriptor.Name("Foo");
- descriptor.Field("bar").Type>();
- }
- }
-
- public class Query
- {
- public string? Scalar { get; }
-
- public string NonNulScalar { get; } = null!;
-
- public string?[]? ScalarArray { get; }
-
- public string[] NonNullScalarArray { get; } = null!;
-
- public string?[] OuterNonNullScalarArray { get; } = null!;
-
- public string?[]?[]? ScalarNestedArray { get; }
-
- public string[][] NonNullScalarNestedArray { get; } = null!;
-
- public string[]?[] InnerNonNullScalarNestedArray { get; } = null!;
-
- public Foo? Object { get; }
-
- public Foo NonNullObject { get; } = null!;
-
- public Foo?[]? ObjectArray { get; }
-
- public Foo[] NonNullObjectArray { get; } = null!;
-
- public Foo?[]?[]? ObjectNestedArray { get; }
-
- public Foo[][] NonNullObjectNestedArray { get; } = null!;
-
- public Foo[]?[] InnerNonNullObjectNestedArray { get; } = null!;
- }
-
- [ObjectType("Query")]
- public class QueryWithTypeAttribute
- {
- [GraphQLType]
- public string? Scalar { get; }
-
- [GraphQLType>]
- public string NonNulScalar { get; } = null!;
-
- [GraphQLType>]
- public string?[]? ScalarArray { get; }
-
- [GraphQLType>>>]
- public string[] NonNullScalarArray { get; } = null!;
-
- [GraphQLType>>]
- public string?[] OuterNonNullScalarArray { get; } = null!;
-
- [GraphQLType>>]
- public string?[]?[]? ScalarNestedArray { get; }
-
- [GraphQLType>>>>>]
- public string[][] NonNullScalarNestedArray { get; } = null!;
-
- [GraphQLType>>>>]
- public string[]?[] InnerNonNullScalarNestedArray { get; } = null!;
-
- [GraphQLType]
- public Foo? Object { get; }
-
- [GraphQLType>]
- public Foo NonNullObject { get; } = null!;
-
- [GraphQLType>]
- public Foo?[]? ObjectArray { get; }
-
- [GraphQLType>>>]
- public Foo[] NonNullObjectArray { get; } = null!;
-
- [GraphQLType>>]
- public Foo?[]?[]? ObjectNestedArray { get; }
-
- [GraphQLType>>>>>]
- public Foo[][] NonNullObjectNestedArray { get; } = null!;
-
- [GraphQLType>>>>]
- public Foo[]?[] InnerNonNullObjectNestedArray { get; } = null!;
- }
-
- [ObjectType("Query")]
- public class QueryWithTypeAttributeAsString
- {
- [GraphQLType("String")]
- public string? Scalar { get; }
-
- [GraphQLType("String!")]
- public string NonNulScalar { get; } = null!;
-
- [GraphQLType("[String]")]
- public string?[]? ScalarArray { get; }
-
- [GraphQLType("[String!]!")]
- public string[] NonNullScalarArray { get; } = null!;
-
- [GraphQLType("[String]!")]
- public string?[] OuterNonNullScalarArray { get; } = null!;
-
- [GraphQLType("[[String]]")]
- public string?[]?[]? ScalarNestedArray { get; }
-
- [GraphQLType("[[String!]!]!")]
- public string[][] NonNullScalarNestedArray { get; } = null!;
-
- [GraphQLType("[[String!]]!")]
- public string[]?[] InnerNonNullScalarNestedArray { get; } = null!;
-
- [GraphQLType("Foo")]
- public Foo? Object { get; }
-
- [GraphQLType("Foo!")]
- public Foo NonNullObject { get; } = null!;
-
- [GraphQLType("[Foo]")]
- public Foo?[]? ObjectArray { get; }
-
- [GraphQLType("[Foo!]!")]
- public Foo[] NonNullObjectArray { get; } = null!;
-
- [GraphQLType("[[Foo]]")]
- public Foo?[]?[]? ObjectNestedArray { get; }
-
- [GraphQLType("[[Foo!]!]!")]
- public Foo[][] NonNullObjectNestedArray { get; } = null!;
-
- [GraphQLType("[[Foo!]]!")]
- public Foo[]?[] InnerNonNullObjectNestedArray { get; } = null!;
- }
-
- public class Foo
- {
- public string Bar { get; } = null!;
- }
-
- [ObjectType("Query")]
- public class QueryWithTypeWithId
- {
- public MyType GetMyNode() => new(1);
- }
-
- public record MyType([property: ID] int Id);
-
- public class Mutation
- {
- [UseMutationConvention]
- [Error]
- public bool DoSomething() => true;
- }
-
- [ExtendObjectType]
- public class MutationExtensions
- {
- public bool DoSomethingElse() => true;
- }
-
- public class MyException : Exception;
-
- public class QueryWithPagination
- {
- [UsePaging]
- public string[] GetCursorPagination() => [];
-
- [UseOffsetPaging]
- public string[] GetOffsetPagination() => [];
- }
-}
diff --git a/src/HotChocolate/Core/test/Types.Tests/__snapshots__/SemanticNonNullTests.Apply_SemanticNonNull_To_SchemaFirst.snap b/src/HotChocolate/Core/test/Types.Tests/__snapshots__/SemanticNonNullTests.Apply_SemanticNonNull_To_SchemaFirst.snap
deleted file mode 100644
index 0245e09f387..00000000000
--- a/src/HotChocolate/Core/test/Types.Tests/__snapshots__/SemanticNonNullTests.Apply_SemanticNonNull_To_SchemaFirst.snap
+++ /dev/null
@@ -1,29 +0,0 @@
-schema {
- query: Query
-}
-
-type Foo {
- bar: String @semanticNonNull
-}
-
-type Query {
- scalar: String
- nonNulScalar: String @semanticNonNull
- scalarArray: [String]
- nonNullScalarArray: [String] @semanticNonNull(levels: [0, 1])
- outerNonNullScalarArray: [String] @semanticNonNull
- scalarNestedArray: [[String]]
- nonNullScalarNestedArray: [[String]] @semanticNonNull(levels: [0, 1, 2])
- innerNonNullScalarNestedArray: [[String]] @semanticNonNull(levels: [0, 2])
- object: Foo
- nonNullObject: Foo @semanticNonNull
- objectArray: [Foo]
- nonNullObjectArray: [Foo] @semanticNonNull(levels: [0, 1])
- objectNestedArray: [[Foo]]
- nonNullObjectNestedArray: [[Foo]] @semanticNonNull(levels: [0, 1, 2])
- innerNonNullObjectNestedArray: [[Foo]] @semanticNonNull(levels: [0, 2])
-}
-
-directive @semanticNonNull(levels: [Int!] = [
- 0
-]) on FIELD_DEFINITION
diff --git a/src/HotChocolate/Core/test/Types.Tests/__snapshots__/SemanticNonNullTests.Derive_SemanticNonNull_From_CodeFirst.snap b/src/HotChocolate/Core/test/Types.Tests/__snapshots__/SemanticNonNullTests.Derive_SemanticNonNull_From_CodeFirst.snap
deleted file mode 100644
index 0245e09f387..00000000000
--- a/src/HotChocolate/Core/test/Types.Tests/__snapshots__/SemanticNonNullTests.Derive_SemanticNonNull_From_CodeFirst.snap
+++ /dev/null
@@ -1,29 +0,0 @@
-schema {
- query: Query
-}
-
-type Foo {
- bar: String @semanticNonNull
-}
-
-type Query {
- scalar: String
- nonNulScalar: String @semanticNonNull
- scalarArray: [String]
- nonNullScalarArray: [String] @semanticNonNull(levels: [0, 1])
- outerNonNullScalarArray: [String] @semanticNonNull
- scalarNestedArray: [[String]]
- nonNullScalarNestedArray: [[String]] @semanticNonNull(levels: [0, 1, 2])
- innerNonNullScalarNestedArray: [[String]] @semanticNonNull(levels: [0, 2])
- object: Foo
- nonNullObject: Foo @semanticNonNull
- objectArray: [Foo]
- nonNullObjectArray: [Foo] @semanticNonNull(levels: [0, 1])
- objectNestedArray: [[Foo]]
- nonNullObjectNestedArray: [[Foo]] @semanticNonNull(levels: [0, 1, 2])
- innerNonNullObjectNestedArray: [[Foo]] @semanticNonNull(levels: [0, 2])
-}
-
-directive @semanticNonNull(levels: [Int!] = [
- 0
-]) on FIELD_DEFINITION
diff --git a/src/HotChocolate/Core/test/Types.Tests/__snapshots__/SemanticNonNullTests.Derive_SemanticNonNull_From_ImplementationFirst.snap b/src/HotChocolate/Core/test/Types.Tests/__snapshots__/SemanticNonNullTests.Derive_SemanticNonNull_From_ImplementationFirst.snap
deleted file mode 100644
index 0245e09f387..00000000000
--- a/src/HotChocolate/Core/test/Types.Tests/__snapshots__/SemanticNonNullTests.Derive_SemanticNonNull_From_ImplementationFirst.snap
+++ /dev/null
@@ -1,29 +0,0 @@
-schema {
- query: Query
-}
-
-type Foo {
- bar: String @semanticNonNull
-}
-
-type Query {
- scalar: String
- nonNulScalar: String @semanticNonNull
- scalarArray: [String]
- nonNullScalarArray: [String] @semanticNonNull(levels: [0, 1])
- outerNonNullScalarArray: [String] @semanticNonNull
- scalarNestedArray: [[String]]
- nonNullScalarNestedArray: [[String]] @semanticNonNull(levels: [0, 1, 2])
- innerNonNullScalarNestedArray: [[String]] @semanticNonNull(levels: [0, 2])
- object: Foo
- nonNullObject: Foo @semanticNonNull
- objectArray: [Foo]
- nonNullObjectArray: [Foo] @semanticNonNull(levels: [0, 1])
- objectNestedArray: [[Foo]]
- nonNullObjectNestedArray: [[Foo]] @semanticNonNull(levels: [0, 1, 2])
- innerNonNullObjectNestedArray: [[Foo]] @semanticNonNull(levels: [0, 2])
-}
-
-directive @semanticNonNull(levels: [Int!] = [
- 0
-]) on FIELD_DEFINITION
diff --git a/src/HotChocolate/Core/test/Types.Tests/__snapshots__/SemanticNonNullTests.Derive_SemanticNonNull_From_ImplementationFirst_With_GraphQLType_As_String.snap b/src/HotChocolate/Core/test/Types.Tests/__snapshots__/SemanticNonNullTests.Derive_SemanticNonNull_From_ImplementationFirst_With_GraphQLType_As_String.snap
deleted file mode 100644
index 0245e09f387..00000000000
--- a/src/HotChocolate/Core/test/Types.Tests/__snapshots__/SemanticNonNullTests.Derive_SemanticNonNull_From_ImplementationFirst_With_GraphQLType_As_String.snap
+++ /dev/null
@@ -1,29 +0,0 @@
-schema {
- query: Query
-}
-
-type Foo {
- bar: String @semanticNonNull
-}
-
-type Query {
- scalar: String
- nonNulScalar: String @semanticNonNull
- scalarArray: [String]
- nonNullScalarArray: [String] @semanticNonNull(levels: [0, 1])
- outerNonNullScalarArray: [String] @semanticNonNull
- scalarNestedArray: [[String]]
- nonNullScalarNestedArray: [[String]] @semanticNonNull(levels: [0, 1, 2])
- innerNonNullScalarNestedArray: [[String]] @semanticNonNull(levels: [0, 2])
- object: Foo
- nonNullObject: Foo @semanticNonNull
- objectArray: [Foo]
- nonNullObjectArray: [Foo] @semanticNonNull(levels: [0, 1])
- objectNestedArray: [[Foo]]
- nonNullObjectNestedArray: [[Foo]] @semanticNonNull(levels: [0, 1, 2])
- innerNonNullObjectNestedArray: [[Foo]] @semanticNonNull(levels: [0, 2])
-}
-
-directive @semanticNonNull(levels: [Int!] = [
- 0
-]) on FIELD_DEFINITION
diff --git a/src/HotChocolate/Core/test/Types.Tests/__snapshots__/SemanticNonNullTests.Derive_SemanticNonNull_From_ImplementationFirst_With_GraphQLType_As_Type.snap b/src/HotChocolate/Core/test/Types.Tests/__snapshots__/SemanticNonNullTests.Derive_SemanticNonNull_From_ImplementationFirst_With_GraphQLType_As_Type.snap
deleted file mode 100644
index 0245e09f387..00000000000
--- a/src/HotChocolate/Core/test/Types.Tests/__snapshots__/SemanticNonNullTests.Derive_SemanticNonNull_From_ImplementationFirst_With_GraphQLType_As_Type.snap
+++ /dev/null
@@ -1,29 +0,0 @@
-schema {
- query: Query
-}
-
-type Foo {
- bar: String @semanticNonNull
-}
-
-type Query {
- scalar: String
- nonNulScalar: String @semanticNonNull
- scalarArray: [String]
- nonNullScalarArray: [String] @semanticNonNull(levels: [0, 1])
- outerNonNullScalarArray: [String] @semanticNonNull
- scalarNestedArray: [[String]]
- nonNullScalarNestedArray: [[String]] @semanticNonNull(levels: [0, 1, 2])
- innerNonNullScalarNestedArray: [[String]] @semanticNonNull(levels: [0, 2])
- object: Foo
- nonNullObject: Foo @semanticNonNull
- objectArray: [Foo]
- nonNullObjectArray: [Foo] @semanticNonNull(levels: [0, 1])
- objectNestedArray: [[Foo]]
- nonNullObjectNestedArray: [[Foo]] @semanticNonNull(levels: [0, 1, 2])
- innerNonNullObjectNestedArray: [[Foo]] @semanticNonNull(levels: [0, 2])
-}
-
-directive @semanticNonNull(levels: [Int!] = [
- 0
-]) on FIELD_DEFINITION
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
deleted file mode 100644
index dbc228ece25..00000000000
--- a/src/HotChocolate/Core/test/Types.Tests/__snapshots__/SemanticNonNullTests.Interface_With_Id_Field.snap
+++ /dev/null
@@ -1,30 +0,0 @@
-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
diff --git a/src/HotChocolate/Core/test/Types.Tests/__snapshots__/SemanticNonNullTests.MutationConventions.snap b/src/HotChocolate/Core/test/Types.Tests/__snapshots__/SemanticNonNullTests.MutationConventions.snap
deleted file mode 100644
index b70e8766c8c..00000000000
--- a/src/HotChocolate/Core/test/Types.Tests/__snapshots__/SemanticNonNullTests.MutationConventions.snap
+++ /dev/null
@@ -1,27 +0,0 @@
-schema {
- mutation: Mutation
-}
-
-interface Error {
- message: String @semanticNonNull
-}
-
-type DoSomethingPayload {
- boolean: Boolean
- errors: [DoSomethingError] @semanticNonNull(levels: [1])
-}
-
-type Mutation {
- doSomething: DoSomethingPayload!
- doSomethingElse: Boolean!
-}
-
-type MyError implements Error {
- message: String @semanticNonNull
-}
-
-union DoSomethingError = MyError
-
-directive @semanticNonNull(levels: [Int!] = [
- 0
-]) on FIELD_DEFINITION
diff --git a/src/HotChocolate/Core/test/Types.Tests/__snapshots__/SemanticNonNullTests.Object_With_Id_Field.snap b/src/HotChocolate/Core/test/Types.Tests/__snapshots__/SemanticNonNullTests.Object_With_Id_Field.snap
deleted file mode 100644
index a5c210d73ae..00000000000
--- a/src/HotChocolate/Core/test/Types.Tests/__snapshots__/SemanticNonNullTests.Object_With_Id_Field.snap
+++ /dev/null
@@ -1,15 +0,0 @@
-schema {
- query: Query
-}
-
-type MyType {
- id: ID!
-}
-
-type Query {
- myNode: MyType @semanticNonNull
-}
-
-directive @semanticNonNull(levels: [Int!] = [
- 0
-]) on FIELD_DEFINITION
diff --git a/src/HotChocolate/Core/test/Types.Tests/__snapshots__/SemanticNonNullTests.Pagination.snap b/src/HotChocolate/Core/test/Types.Tests/__snapshots__/SemanticNonNullTests.Pagination.snap
deleted file mode 100644
index 46ffb4b0661..00000000000
--- a/src/HotChocolate/Core/test/Types.Tests/__snapshots__/SemanticNonNullTests.Pagination.snap
+++ /dev/null
@@ -1,67 +0,0 @@
-schema {
- query: QueryWithPagination
-}
-
-"Information about the offset pagination."
-type CollectionSegmentInfo {
- "Indicates whether more items exist following the set defined by the clients arguments."
- hasNextPage: Boolean!
- "Indicates whether more items exist prior the set defined by the clients arguments."
- hasPreviousPage: Boolean!
-}
-
-"A connection to a list of items."
-type CursorPaginationConnection {
- "Information to aid in pagination."
- pageInfo: PageInfo @semanticNonNull
- "A list of edges."
- edges: [CursorPaginationEdge] @semanticNonNull(levels: [1])
- "A flattened list of the nodes."
- nodes: [String] @semanticNonNull(levels: [1])
-}
-
-"An edge in a connection."
-type CursorPaginationEdge {
- "A cursor for use in pagination."
- cursor: String @semanticNonNull
- "The item at the end of the edge."
- node: String @semanticNonNull
-}
-
-"A segment of a collection."
-type OffsetPaginationCollectionSegment {
- "Information to aid in pagination."
- pageInfo: CollectionSegmentInfo @semanticNonNull
- "A flattened list of the items."
- items: [String] @semanticNonNull(levels: [1])
-}
-
-"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
-}
-
-type QueryWithPagination {
- cursorPagination(
- "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
- ): CursorPaginationConnection
- offsetPagination(skip: Int, take: Int): OffsetPaginationCollectionSegment
-}
-
-directive @semanticNonNull(levels: [Int!] = [
- 0
-]) on FIELD_DEFINITION
diff --git a/src/HotChocolate/Diagnostics/src/Diagnostics.Core/Spans/Http/ExecuteHttpRequestSpan.cs b/src/HotChocolate/Diagnostics/src/Diagnostics.Core/Spans/Http/ExecuteHttpRequestSpan.cs
index 21ea1c134b1..107c68e0100 100644
--- a/src/HotChocolate/Diagnostics/src/Diagnostics.Core/Spans/Http/ExecuteHttpRequestSpan.cs
+++ b/src/HotChocolate/Diagnostics/src/Diagnostics.Core/Spans/Http/ExecuteHttpRequestSpan.cs
@@ -43,6 +43,9 @@ internal sealed class ExecuteHttpRequestSpan(
case HttpRequestKind.HttpGetSchema:
activity.DisplayName = "GraphQL HTTP GET SDL";
break;
+ case HttpRequestKind.HttpGetSemanticNonNullSchema:
+ activity.DisplayName = "GraphQL HTTP GET Semantic Non-Null SDL";
+ break;
}
activity.SetTag(GraphQL.Http.Kind, kind.ToString());
diff --git a/src/HotChocolate/Fusion/src/Fusion.Execution.Types/Serialization/SchemaFormatter.cs b/src/HotChocolate/Fusion/src/Fusion.Execution.Types/Serialization/SchemaFormatter.cs
index 1d1a16e2a6a..574b8a09897 100644
--- a/src/HotChocolate/Fusion/src/Fusion.Execution.Types/Serialization/SchemaFormatter.cs
+++ b/src/HotChocolate/Fusion/src/Fusion.Execution.Types/Serialization/SchemaFormatter.cs
@@ -19,21 +19,14 @@ public static string FormatAsString(
ISchemaDefinition schema,
SchemaFormatterOptions options = default)
{
- var context = new VisitorContext
- {
- Schema = schema,
- OrderByName = options.OrderByName ?? true,
- PrintSpecScalars = options.PrintSpecScalars ?? false,
- PrintSpecDirectives = options.PrintSpecDirectives ?? false
- };
- s_visitor.VisitSchema(schema, context);
+ var document = FormatAsDocument(schema, options);
- if (!options.Indented ?? true)
+ if (options.Indented == false)
{
- ((DocumentNode)context.Result!).ToString(false);
+ return document.ToString(false);
}
- return ((DocumentNode)context.Result!).ToString(s_options);
+ return document.ToString(s_options);
}
public static DocumentNode FormatAsDocument(
diff --git a/src/HotChocolate/Mutable/src/Types.Mutable/Serialization/SchemaFormatter.cs b/src/HotChocolate/Mutable/src/Types.Mutable/Serialization/SchemaFormatter.cs
index 9ceb8c984ac..69e06a41e2c 100644
--- a/src/HotChocolate/Mutable/src/Types.Mutable/Serialization/SchemaFormatter.cs
+++ b/src/HotChocolate/Mutable/src/Types.Mutable/Serialization/SchemaFormatter.cs
@@ -15,21 +15,14 @@ public static class SchemaFormatter
public static string FormatAsString(MutableSchemaDefinition schema, SchemaFormatterOptions options = default)
{
- var context = new VisitorContext
- {
- Schema = schema,
- OrderByName = options.OrderByName ?? true,
- PrintSpecScalars = options.PrintSpecScalars ?? false,
- PrintSpecDirectives = options.PrintSpecDirectives ?? false
- };
- s_visitor.VisitSchema(schema, context);
+ var document = FormatAsDocument(schema, options);
- if (!options.Indented ?? true)
+ if (options.Indented == false)
{
- ((DocumentNode)context.Result!).ToString(false);
+ return document.ToString(false);
}
- return ((DocumentNode)context.Result!).ToString(s_options);
+ return document.ToString(s_options);
}
public static DocumentNode FormatAsDocument(MutableSchemaDefinition schema, SchemaFormatterOptions options = default)
diff --git a/src/HotChocolate/Primitives/src/Primitives/ErrorCodes.cs b/src/HotChocolate/Primitives/src/Primitives/ErrorCodes.cs
index 8cce837b4a5..fa8d37e3b5c 100644
--- a/src/HotChocolate/Primitives/src/Primitives/ErrorCodes.cs
+++ b/src/HotChocolate/Primitives/src/Primitives/ErrorCodes.cs
@@ -49,7 +49,6 @@ public static class Execution
public const string OneSlicingArgumentRequired = "HC0082";
public const string NonNullViolation = "HC0018";
- public const string SemanticNonNullViolation = "HC0088";
public const string MustBeInputType = "HC0017";
public const string InvalidType = "HC0016";
public const string OperationDocumentNotFound = "HC0015";