diff --git a/src/HotChocolate/Core/src/Abstractions/ErrorCodes.cs b/src/HotChocolate/Core/src/Abstractions/ErrorCodes.cs
index dad7fd68e68..d18b87831d1 100644
--- a/src/HotChocolate/Core/src/Abstractions/ErrorCodes.cs
+++ b/src/HotChocolate/Core/src/Abstractions/ErrorCodes.cs
@@ -49,6 +49,7 @@ 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 QueryNotFound = "HC0015";
diff --git a/src/HotChocolate/Core/src/Abstractions/WellKnownDirectives.cs b/src/HotChocolate/Core/src/Abstractions/WellKnownDirectives.cs
index 9126408eb4b..ed741877439 100644
--- a/src/HotChocolate/Core/src/Abstractions/WellKnownDirectives.cs
+++ b/src/HotChocolate/Core/src/Abstractions/WellKnownDirectives.cs
@@ -69,4 +69,14 @@ public static class WellKnownDirectives
/// The name of the @tag argument name.
///
public const string Name = "name";
+
+ ///
+ /// The name of the @semanticNonNull directive.
+ ///
+ public const string SemanticNonNull = "semanticNonNull";
+
+ ///
+ /// The name of the @semanticNonNull argument levels.
+ ///
+ public const string Levels = "levels";
}
diff --git a/src/HotChocolate/Core/src/Abstractions/WellKnownMiddleware.cs b/src/HotChocolate/Core/src/Abstractions/WellKnownMiddleware.cs
index d7d31265e9d..be6fa2bb050 100644
--- a/src/HotChocolate/Core/src/Abstractions/WellKnownMiddleware.cs
+++ b/src/HotChocolate/Core/src/Abstractions/WellKnownMiddleware.cs
@@ -90,4 +90,9 @@ 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/IReadOnlySchemaOptions.cs b/src/HotChocolate/Core/src/Types/IReadOnlySchemaOptions.cs
index 78b8062ffe9..6509c7e4614 100644
--- a/src/HotChocolate/Core/src/Types/IReadOnlySchemaOptions.cs
+++ b/src/HotChocolate/Core/src/Types/IReadOnlySchemaOptions.cs
@@ -165,6 +165,13 @@ 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; }
+
///
/// Specifies the maximum allowed nodes that can be fetched at once through the nodes field.
///
diff --git a/src/HotChocolate/Core/src/Types/SchemaBuilder.cs b/src/HotChocolate/Core/src/Types/SchemaBuilder.cs
index e538d629c02..b34a54db89b 100644
--- a/src/HotChocolate/Core/src/Types/SchemaBuilder.cs
+++ b/src/HotChocolate/Core/src/Types/SchemaBuilder.cs
@@ -37,6 +37,7 @@ public partial class SchemaBuilder : ISchemaBuilder
typeof(InterfaceCompletionTypeInterceptor),
typeof(MiddlewareValidationTypeInterceptor),
typeof(EnableTrueNullabilityTypeInterceptor),
+ typeof(SemanticNonNullTypeInterceptor),
];
private SchemaOptions _options = new();
diff --git a/src/HotChocolate/Core/src/Types/SchemaOptions.cs b/src/HotChocolate/Core/src/Types/SchemaOptions.cs
index 800c9e0f827..eb4b277f829 100644
--- a/src/HotChocolate/Core/src/Types/SchemaOptions.cs
+++ b/src/HotChocolate/Core/src/Types/SchemaOptions.cs
@@ -15,58 +15,34 @@ public class SchemaOptions : IReadOnlySchemaOptions
private BindingBehavior _defaultBindingBehavior = BindingBehavior.Implicit;
private FieldBindingFlags _defaultFieldBindingFlags = FieldBindingFlags.Instance;
- ///
- /// Gets or sets the name of the query type.
- ///
+ ///
public string? QueryTypeName { get; set; }
- ///
- /// Gets or sets the name of the mutation type.
- ///
+ ///
public string? MutationTypeName { get; set; }
- ///
- /// Gets or sets the name of the subscription type.
- ///
+ ///
public string? SubscriptionTypeName { get; set; }
- ///
- /// Defines if the schema allows the query type to be omitted.
- ///
+ ///
public bool StrictValidation { get; set; } = true;
- ///
- /// Defines if the CSharp XML documentation shall be integrated.
- ///
+ ///
public bool UseXmlDocumentation { get; set; } = true;
- ///
- /// A delegate which defines the name of the XML documentation file to be read.
- /// Only used if is true.
- ///
+ ///
public Func? ResolveXmlDocumentationFileName { get; set; }
- ///
- /// Defines if fields shall be sorted by name.
- /// Default: false
- ///
+ ///
public bool SortFieldsByName { get; set; }
- ///
- /// Defines if types shall be removed from the schema that are
- /// unreachable from the root types.
- ///
+ ///
public bool RemoveUnreachableTypes { get; set; }
- ///
- /// Defines if unused type system directives shall
- /// be removed from the schema.
- ///
+ ///
public bool RemoveUnusedTypeSystemDirectives { get; set; } = true;
- ///
- /// Defines the default binding behavior.
- ///
+ ///
public BindingBehavior DefaultBindingBehavior
{
get => _defaultBindingBehavior;
@@ -81,10 +57,7 @@ public BindingBehavior DefaultBindingBehavior
}
}
- ///
- /// Defines which members shall be by default inferred as GraphQL fields.
- /// This default applies to and .
- ///
+ ///
public FieldBindingFlags DefaultFieldBindingFlags
{
get => _defaultFieldBindingFlags;
@@ -99,137 +72,69 @@ public FieldBindingFlags DefaultFieldBindingFlags
}
}
- ///
- /// Defines on which fields a middleware pipeline can be applied on.
- ///
+ ///
public FieldMiddlewareApplication FieldMiddleware { get; set; } =
FieldMiddlewareApplication.UserDefinedFields;
- ///
- /// Defines if the experimental directive introspection feature shall be enabled.
- ///
+ ///
public bool EnableDirectiveIntrospection { get; set; }
- ///
- /// The default directive visibility when directive introspection is enabled.
- ///
+ ///
public DirectiveVisibility DefaultDirectiveVisibility { get; set; } =
DirectiveVisibility.Public;
- ///
- /// Defines that the default resolver execution strategy.
- ///
+ ///
public ExecutionStrategy DefaultResolverStrategy { get; set; } =
ExecutionStrategy.Parallel;
- ///
- /// Defines if the order of important middleware components shall be validated.
- ///
+ ///
public bool ValidatePipelineOrder { get; set; } = true;
- ///
- /// Defines if the runtime types of types shall be validated.
- ///
+ ///
public bool StrictRuntimeTypeValidation { get; set; }
- ///
- /// Defines a delegate that determines if a runtime
- /// is an instance of an .
- ///
+ ///
public IsOfTypeFallback? DefaultIsOfTypeCheck { get; set; }
- ///
- /// Defines if the OneOf spec RFC is enabled. This feature is experimental.
- ///
+ ///
public bool EnableOneOf { get; set; }
- ///
- /// Defines if the schema building process shall validate that all nodes are resolvable through `node`.
- ///
+ ///
public bool EnsureAllNodesCanBeResolved { get; set; } = true;
- ///
- /// Defines if flag enums should be inferred as object value nodes
- ///
- ///
- /// Given the following enum
- ///
- ///
- /// [Flags]
- /// public enum Example { First, Second, Third }
- ///
- /// public class Query { public Example Loopback(Example input) => input;
- ///
- ///
- /// The following schema is produced
- ///
- ///
- /// type Query {
- /// loopback(input: ExampleFlagsInput!): ExampleFlags
- /// }
- ///
- /// type ExampleFlags {
- /// isFirst: Boolean!
- /// isSecond: Boolean!
- /// isThird: Boolean!
- /// }
- ///
- /// input ExampleFlagsInput {
- /// isFirst: Boolean
- /// isSecond: Boolean
- /// isThird: Boolean
- /// }
- ///
- ///
+ ///
public bool EnableFlagEnums { get; set; }
- ///
- /// Enables the @defer directive.
- /// Defer and stream both are at the moment preview features.
- ///
+ ///
public bool EnableDefer { get; set; }
- ///
- /// Enables the @stream directive.
- /// Defer and stream both are at the moment preview features.
- ///
+ ///
public bool EnableStream { get; set; }
- ///
- /// Specifies the maximum allowed nodes that can be fetched at once through the nodes field.
- ///
+ ///
+ public bool EnableSemanticNonNull { get; set; }
+
+ ///
public int MaxAllowedNodeBatchSize { get; set; } = 50;
- ///
- /// Specified if the leading I shall be stripped from the interface name.
- ///
+ ///
public bool StripLeadingIFromInterface { get; set; }
- ///
- /// Specifies that the true nullability proto type shall be enabled.
- ///
+ ///
public bool EnableTrueNullability { get; set; }
- ///
- /// Specifies that the @tag directive shall be registered with the type system.
- ///
+ ///
public bool EnableTag { get; set; } = true;
- ///
- /// Defines the default dependency injection scope for query fields.
- ///
+ ///
public DependencyInjectionScope DefaultQueryDependencyInjectionScope { get; set; } =
DependencyInjectionScope.Resolver;
- ///
- /// Defines the default dependency injection scope for mutation fields.
- ///
+ ///
public DependencyInjectionScope DefaultMutationDependencyInjectionScope { get; set; } =
DependencyInjectionScope.Request;
- ///
- /// Defines if the root field pages shall be published to the promise cache.
- ///
+ ///
public bool PublishRootFieldPagesToPromiseCache { get; set; } = true;
///
@@ -263,6 +168,7 @@ public static SchemaOptions FromOptions(IReadOnlySchemaOptions options)
EnableFlagEnums = options.EnableFlagEnums,
EnableDefer = options.EnableDefer,
EnableStream = options.EnableStream,
+ EnableSemanticNonNull = options.EnableSemanticNonNull,
DefaultFieldBindingFlags = options.DefaultFieldBindingFlags,
MaxAllowedNodeBatchSize = options.MaxAllowedNodeBatchSize,
StripLeadingIFromInterface = options.StripLeadingIFromInterface,
diff --git a/src/HotChocolate/Core/src/Types/SemanticNonNullTypeInterceptor.cs b/src/HotChocolate/Core/src/Types/SemanticNonNullTypeInterceptor.cs
new file mode 100644
index 00000000000..1be7f69f7bf
--- /dev/null
+++ b/src/HotChocolate/Core/src/Types/SemanticNonNullTypeInterceptor.cs
@@ -0,0 +1,376 @@
+#nullable enable
+
+using System.Collections;
+using HotChocolate.Configuration;
+using HotChocolate.Language;
+using HotChocolate.Resolvers;
+using HotChocolate.Types;
+using HotChocolate.Types.Descriptors;
+using HotChocolate.Types.Descriptors.Definitions;
+using HotChocolate.Types.Helpers;
+using HotChocolate.Types.Relay;
+
+namespace HotChocolate;
+
+internal sealed class SemanticNonNullTypeInterceptor : TypeInterceptor
+{
+ private ITypeInspector _typeInspector = null!;
+ private ExtendedTypeReference _nodeTypeReference = null!;
+
+ internal 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;
+
+ _nodeTypeReference = _typeInspector.GetTypeRef(typeof(NodeType));
+ }
+
+ ///
+ /// After the root types have been resolved, we go through all the fields of the mutation type
+ /// and undo semantic non-nullability. This is because mutations can be chained and we want to retain
+ /// the null-bubbling so execution is aborted once one non-null mutation field produces an error.
+ /// We have to do this in a different hook because the mutation type is not yet fully resolved in the
+ /// hook.
+ ///
+ public override void OnAfterResolveRootType(
+ ITypeCompletionContext completionContext,
+ ObjectTypeDefinition definition,
+ OperationType operationType)
+ {
+ if (operationType == OperationType.Mutation)
+ {
+ foreach (var field in definition.Fields)
+ {
+ if (field.IsIntrospectionField)
+ {
+ continue;
+ }
+
+ if (!field.HasDirectives)
+ {
+ continue;
+ }
+
+ var semanticNonNullDirective =
+ field.Directives.FirstOrDefault(d => d.Value is SemanticNonNullDirective);
+
+ if (semanticNonNullDirective is not null)
+ {
+ field.Directives.Remove(semanticNonNullDirective);
+ }
+
+ var semanticNonNullFormatterDefinition =
+ field.FormatterDefinitions.FirstOrDefault(fd => fd.Key == WellKnownMiddleware.SemanticNonNull);
+
+ if (semanticNonNullFormatterDefinition is not null)
+ {
+ field.FormatterDefinitions.Remove(semanticNonNullFormatterDefinition);
+ }
+ }
+ }
+ }
+
+ public override void OnAfterCompleteName(ITypeCompletionContext completionContext, DefinitionBase definition)
+ {
+ if (completionContext.IsIntrospectionType)
+ {
+ return;
+ }
+
+ if (definition is ObjectTypeDefinition objectDef)
+ {
+ if (objectDef.Name is "CollectionSegmentInfo" or "PageInfo")
+ {
+ return;
+ }
+
+ var implementsNode = objectDef.Interfaces.Any(i => i.Equals(_nodeTypeReference));
+
+ foreach (var field in objectDef.Fields)
+ {
+ if (field.IsIntrospectionField)
+ {
+ continue;
+ }
+
+ if (implementsNode && field.Name == "id")
+ {
+ continue;
+ }
+
+ if (field.Type is null)
+ {
+ continue;
+ }
+
+ var levels = GetSemanticNonNullLevels(field.Type);
+
+ if (levels.Count < 1)
+ {
+ continue;
+ }
+
+ ApplySemanticNonNullDirective(field, completionContext, levels);
+
+ field.FormatterDefinitions.Add(CreateSemanticNonNullResultFormatterDefinition(levels));
+ }
+ }
+ else if (definition is InterfaceTypeDefinition interfaceDef)
+ {
+ if (interfaceDef.Name == "Node")
+ {
+ // The Node interface is well defined, so we don't want to go and change the type of its fields.
+ return;
+ }
+
+ foreach (var field in interfaceDef.Fields)
+ {
+ if (field.Type is null)
+ {
+ continue;
+ }
+
+ var levels = GetSemanticNonNullLevels(field.Type);
+
+ if (levels.Count < 1)
+ {
+ continue;
+ }
+
+ ApplySemanticNonNullDirective(field, completionContext, levels);
+ }
+ }
+ }
+
+ private void ApplySemanticNonNullDirective(
+ OutputFieldDefinitionBase 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.Type;
+ }
+ 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?[] _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,
+ _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.Type);
+ }
+
+ 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 ResultFormatterDefinition CreateSemanticNonNullResultFormatterDefinition(HashSet levels)
+ => new((context, result) =>
+ {
+ CheckResultForSemanticNonNullViolations(result, context, context.Path, levels, 0);
+
+ return result;
+ },
+ key: WellKnownMiddleware.SemanticNonNull,
+ isRepeatable: false);
+
+ 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/Directives.cs b/src/HotChocolate/Core/src/Types/Types/Directives/Directives.cs
index 716b0259114..362a883a4d1 100644
--- a/src/HotChocolate/Core/src/Types/Types/Directives/Directives.cs
+++ b/src/HotChocolate/Core/src/Types/Types/Directives/Directives.cs
@@ -15,6 +15,7 @@ public static class Directives
WellKnownDirectives.Stream,
WellKnownDirectives.Defer,
WellKnownDirectives.OneOf,
+ WellKnownDirectives.SemanticNonNull
];
internal static IReadOnlyList CreateReferences(
@@ -38,6 +39,11 @@ internal static IReadOnlyList CreateReferences(
directiveTypes.Add(typeInspector.GetTypeRef(typeof(StreamDirectiveType)));
}
+ if (descriptorContext.Options.EnableSemanticNonNull)
+ {
+ directiveTypes.Add(typeInspector.GetTypeRef(typeof(SemanticNonNullDirective)));
+ }
+
if (descriptorContext.Options.EnableTag)
{
directiveTypes.Add(typeInspector.GetTypeRef(typeof(Tag)));
diff --git a/src/HotChocolate/Core/src/Types/Types/Directives/SemanticNonNullDirective.cs b/src/HotChocolate/Core/src/Types/Types/Directives/SemanticNonNullDirective.cs
new file mode 100644
index 00000000000..1f64e212a95
--- /dev/null
+++ b/src/HotChocolate/Core/src/Types/Types/Directives/SemanticNonNullDirective.cs
@@ -0,0 +1,11 @@
+#nullable enable
+
+namespace HotChocolate.Types;
+
+[DirectiveType(WellKnownDirectives.SemanticNonNull, 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
new file mode 100644
index 00000000000..48088a0729f
--- /dev/null
+++ b/src/HotChocolate/Core/test/Execution.Tests/SemanticNonNullTests.cs
@@ -0,0 +1,1057 @@
+using CookieCrumble;
+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() => new[] { "a", null!, "c" };
+
+ [UsePaging]
+ public string?[] NullableScalarConnection() => new[] { "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
new file mode 100644
index 00000000000..5bf14273af3
--- /dev/null
+++ b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Nullable_Object_List_Item_Returns_Null_Should_Null_Item_Without_Error.snap
@@ -0,0 +1,13 @@
+{
+ "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
new file mode 100644
index 00000000000..80512055ffc
--- /dev/null
+++ b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Nullable_Object_List_Returns_Null_Should_Null_Without_Error.snap
@@ -0,0 +1,5 @@
+{
+ "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
new file mode 100644
index 00000000000..d6ed49ca517
--- /dev/null
+++ b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Nullable_Object_Returns_Null_Should_Null_Without_Error.snap
@@ -0,0 +1,5 @@
+{
+ "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
new file mode 100644
index 00000000000..b3c51911a4c
--- /dev/null
+++ b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Nullable_Scalar_List_Item_Returns_Null_Should_Null_Item_Without_Error.snap
@@ -0,0 +1,9 @@
+{
+ "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
new file mode 100644
index 00000000000..68f0bbd3b50
--- /dev/null
+++ b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Nullable_Scalar_List_Returns_Null_Should_Null_Without_Error.snap
@@ -0,0 +1,5 @@
+{
+ "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
new file mode 100644
index 00000000000..161eb3e36df
--- /dev/null
+++ b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Nullable_Scalar_Returns_Null_Should_Null_Without_Error.snap
@@ -0,0 +1,5 @@
+{
+ "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
new file mode 100644
index 00000000000..cef5849184d
--- /dev/null
+++ b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Object_List_Item_Returns_Null_Should_Error_Item.snap
@@ -0,0 +1,31 @@
+{
+ "errors": [
+ {
+ "message": "Cannot return null for semantic non-null field.",
+ "locations": [
+ {
+ "line": 2,
+ "column": 3
+ }
+ ],
+ "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
new file mode 100644
index 00000000000..c136aaacabf
--- /dev/null
+++ b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Object_List_Item_Throwing_Should_Null_And_Error_Item.snap
@@ -0,0 +1,44 @@
+{
+ "errors": [
+ {
+ "message": "Another error",
+ "locations": [
+ {
+ "line": 2,
+ "column": 3
+ }
+ ],
+ "path": [
+ "objectListItemThrowingError",
+ 1
+ ]
+ },
+ {
+ "message": "Cannot return null for semantic non-null field.",
+ "locations": [
+ {
+ "line": 2,
+ "column": 3
+ }
+ ],
+ "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
new file mode 100644
index 00000000000..5272b1193ac
--- /dev/null
+++ b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Object_List_Returns_Null_Should_Error.snap
@@ -0,0 +1,22 @@
+{
+ "errors": [
+ {
+ "message": "Cannot return null for semantic non-null field.",
+ "locations": [
+ {
+ "line": 2,
+ "column": 3
+ }
+ ],
+ "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
new file mode 100644
index 00000000000..584bc579f4d
--- /dev/null
+++ b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Object_List_Throwing_Should_Null_FAnd_Error.snap
@@ -0,0 +1,19 @@
+{
+ "errors": [
+ {
+ "message": "Unexpected Execution Error",
+ "locations": [
+ {
+ "line": 2,
+ "column": 3
+ }
+ ],
+ "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
new file mode 100644
index 00000000000..fa19729099c
--- /dev/null
+++ b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Object_Returns_Null_Should_Error.snap
@@ -0,0 +1,22 @@
+{
+ "errors": [
+ {
+ "message": "Cannot return null for semantic non-null field.",
+ "locations": [
+ {
+ "line": 2,
+ "column": 3
+ }
+ ],
+ "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
new file mode 100644
index 00000000000..de64fcf0f25
--- /dev/null
+++ b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Object_Throwing_Should_Null_And_Error.snap
@@ -0,0 +1,19 @@
+{
+ "errors": [
+ {
+ "message": "Unexpected Execution Error",
+ "locations": [
+ {
+ "line": 2,
+ "column": 3
+ }
+ ],
+ "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
new file mode 100644
index 00000000000..6e3b5178b29
--- /dev/null
+++ b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Scalar_List_Item_Returns_Null_Should_Error_Item.snap
@@ -0,0 +1,27 @@
+{
+ "errors": [
+ {
+ "message": "Cannot return null for semantic non-null field.",
+ "locations": [
+ {
+ "line": 2,
+ "column": 3
+ }
+ ],
+ "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
new file mode 100644
index 00000000000..87f194c0037
--- /dev/null
+++ b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Scalar_List_Item_Throwing_Should_Null_And_Error_Item.snap
@@ -0,0 +1,40 @@
+{
+ "errors": [
+ {
+ "message": "Another error",
+ "locations": [
+ {
+ "line": 2,
+ "column": 3
+ }
+ ],
+ "path": [
+ "scalarListItemThrowingError",
+ 1
+ ]
+ },
+ {
+ "message": "Cannot return null for semantic non-null field.",
+ "locations": [
+ {
+ "line": 2,
+ "column": 3
+ }
+ ],
+ "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
new file mode 100644
index 00000000000..136f20e321e
--- /dev/null
+++ b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Scalar_List_Returns_Null_Should_Error.snap
@@ -0,0 +1,22 @@
+{
+ "errors": [
+ {
+ "message": "Cannot return null for semantic non-null field.",
+ "locations": [
+ {
+ "line": 2,
+ "column": 3
+ }
+ ],
+ "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
new file mode 100644
index 00000000000..fe169a3cc33
--- /dev/null
+++ b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Scalar_List_Throwing_Should_Null_And_Error.snap
@@ -0,0 +1,19 @@
+{
+ "errors": [
+ {
+ "message": "Unexpected Execution Error",
+ "locations": [
+ {
+ "line": 2,
+ "column": 3
+ }
+ ],
+ "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
new file mode 100644
index 00000000000..d6f046b374e
--- /dev/null
+++ b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Scalar_Returns_Null_Should_Error.snap
@@ -0,0 +1,22 @@
+{
+ "errors": [
+ {
+ "message": "Cannot return null for semantic non-null field.",
+ "locations": [
+ {
+ "line": 2,
+ "column": 3
+ }
+ ],
+ "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
new file mode 100644
index 00000000000..f992ef3496b
--- /dev/null
+++ b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Async_Scalar_Throwing_Should_Null_And_Error.snap
@@ -0,0 +1,19 @@
+{
+ "errors": [
+ {
+ "message": "Unexpected Execution Error",
+ "locations": [
+ {
+ "line": 2,
+ "column": 3
+ }
+ ],
+ "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
new file mode 100644
index 00000000000..bd13441af00
--- /dev/null
+++ b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Mutation_With_MutationConventions.snap
@@ -0,0 +1,7 @@
+{
+ "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
new file mode 100644
index 00000000000..bc867086e8a
--- /dev/null
+++ b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Nullable_Object_List_Item_Returns_Null_Should_Null_Item_Without_Error.snap
@@ -0,0 +1,13 @@
+{
+ "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
new file mode 100644
index 00000000000..2abd30023ac
--- /dev/null
+++ b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Nullable_Object_List_Returns_Null_Should_Null_Without_Error.snap
@@ -0,0 +1,5 @@
+{
+ "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
new file mode 100644
index 00000000000..472da5015a4
--- /dev/null
+++ b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Nullable_Object_Returns_Null_Should_Null_Without_Error.snap
@@ -0,0 +1,5 @@
+{
+ "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
new file mode 100644
index 00000000000..65e0c7452b1
--- /dev/null
+++ b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Nullable_Scalar_List_Item_Returns_Null_Should_Null_Item_Without_Error.snap
@@ -0,0 +1,9 @@
+{
+ "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
new file mode 100644
index 00000000000..859a7fd908e
--- /dev/null
+++ b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Nullable_Scalar_List_Returns_Null_Should_Null_Without_Error.snap
@@ -0,0 +1,5 @@
+{
+ "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
new file mode 100644
index 00000000000..e05006cc33f
--- /dev/null
+++ b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Nullable_Scalar_Returns_Null_Should_Null_Without_Error.snap
@@ -0,0 +1,5 @@
+{
+ "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
new file mode 100644
index 00000000000..20741ef4898
--- /dev/null
+++ b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Object_List_Item_Returns_Null_Should_Error_Item.snap
@@ -0,0 +1,31 @@
+{
+ "errors": [
+ {
+ "message": "Cannot return null for semantic non-null field.",
+ "locations": [
+ {
+ "line": 2,
+ "column": 3
+ }
+ ],
+ "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
new file mode 100644
index 00000000000..338ea110d60
--- /dev/null
+++ b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Object_List_Item_Throwing_Should_Null_And_Error_Item.snap
@@ -0,0 +1,44 @@
+{
+ "errors": [
+ {
+ "message": "Another error",
+ "locations": [
+ {
+ "line": 2,
+ "column": 3
+ }
+ ],
+ "path": [
+ "pureObjectListItemThrowingError",
+ 1
+ ]
+ },
+ {
+ "message": "Cannot return null for semantic non-null field.",
+ "locations": [
+ {
+ "line": 2,
+ "column": 3
+ }
+ ],
+ "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
new file mode 100644
index 00000000000..dd9c523f560
--- /dev/null
+++ b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Object_List_Returns_Null_Should_Error.snap
@@ -0,0 +1,22 @@
+{
+ "errors": [
+ {
+ "message": "Cannot return null for semantic non-null field.",
+ "locations": [
+ {
+ "line": 2,
+ "column": 3
+ }
+ ],
+ "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
new file mode 100644
index 00000000000..89587618619
--- /dev/null
+++ b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Object_List_Throwing_Should_Null_FAnd_Error.snap
@@ -0,0 +1,19 @@
+{
+ "errors": [
+ {
+ "message": "Unexpected Execution Error",
+ "locations": [
+ {
+ "line": 2,
+ "column": 3
+ }
+ ],
+ "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
new file mode 100644
index 00000000000..81b0b6ca595
--- /dev/null
+++ b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Object_Returns_Null_Should_Error.snap
@@ -0,0 +1,22 @@
+{
+ "errors": [
+ {
+ "message": "Cannot return null for semantic non-null field.",
+ "locations": [
+ {
+ "line": 2,
+ "column": 3
+ }
+ ],
+ "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
new file mode 100644
index 00000000000..76230f78282
--- /dev/null
+++ b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Object_Throwing_Should_Null_And_Error.snap
@@ -0,0 +1,19 @@
+{
+ "errors": [
+ {
+ "message": "Unexpected Execution Error",
+ "locations": [
+ {
+ "line": 2,
+ "column": 3
+ }
+ ],
+ "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
new file mode 100644
index 00000000000..7c42ba3a2a3
--- /dev/null
+++ 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
@@ -0,0 +1,53 @@
+{
+ "errors": [
+ {
+ "message": "Cannot return null for semantic non-null field.",
+ "locations": [
+ {
+ "line": 2,
+ "column": 3
+ }
+ ],
+ "path": [
+ "nestedScalarArrayNullableMiddleItem",
+ 0,
+ 1
+ ],
+ "extensions": {
+ "code": "HC0088"
+ }
+ },
+ {
+ "message": "Cannot return null for semantic non-null field.",
+ "locations": [
+ {
+ "line": 2,
+ "column": 3
+ }
+ ],
+ "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
new file mode 100644
index 00000000000..5b68ea3fafb
--- /dev/null
+++ b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Scalar_ListOfList_Nullable_Outer_And_Inner_Middle_Returns_Null_Should_Null_And_Error.snap
@@ -0,0 +1,35 @@
+{
+ "errors": [
+ {
+ "message": "Cannot return null for semantic non-null field.",
+ "locations": [
+ {
+ "line": 2,
+ "column": 3
+ }
+ ],
+ "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
new file mode 100644
index 00000000000..f3a0b0bb1ca
--- /dev/null
+++ b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Scalar_List_Item_Returns_Null_Should_Error_Item.snap
@@ -0,0 +1,27 @@
+{
+ "errors": [
+ {
+ "message": "Cannot return null for semantic non-null field.",
+ "locations": [
+ {
+ "line": 2,
+ "column": 3
+ }
+ ],
+ "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
new file mode 100644
index 00000000000..e5ecb022752
--- /dev/null
+++ b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Scalar_List_Item_Throwing_Should_Null_And_Error_Item.snap
@@ -0,0 +1,40 @@
+{
+ "errors": [
+ {
+ "message": "Another error",
+ "locations": [
+ {
+ "line": 2,
+ "column": 3
+ }
+ ],
+ "path": [
+ "pureScalarListItemThrowingError",
+ 1
+ ]
+ },
+ {
+ "message": "Cannot return null for semantic non-null field.",
+ "locations": [
+ {
+ "line": 2,
+ "column": 3
+ }
+ ],
+ "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
new file mode 100644
index 00000000000..e6c89302539
--- /dev/null
+++ b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Scalar_List_Returns_Null_Should_Error.snap
@@ -0,0 +1,22 @@
+{
+ "errors": [
+ {
+ "message": "Cannot return null for semantic non-null field.",
+ "locations": [
+ {
+ "line": 2,
+ "column": 3
+ }
+ ],
+ "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
new file mode 100644
index 00000000000..87eb04d1690
--- /dev/null
+++ b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Scalar_List_Throwing_Should_Null_And_Error.snap
@@ -0,0 +1,19 @@
+{
+ "errors": [
+ {
+ "message": "Unexpected Execution Error",
+ "locations": [
+ {
+ "line": 2,
+ "column": 3
+ }
+ ],
+ "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
new file mode 100644
index 00000000000..4ddf157a011
--- /dev/null
+++ b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Scalar_Returns_Null_Should_Error.snap
@@ -0,0 +1,22 @@
+{
+ "errors": [
+ {
+ "message": "Cannot return null for semantic non-null field.",
+ "locations": [
+ {
+ "line": 2,
+ "column": 3
+ }
+ ],
+ "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
new file mode 100644
index 00000000000..a1980169f92
--- /dev/null
+++ b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Pure_Scalar_Throwing_Should_Null_And_Error.snap
@@ -0,0 +1,19 @@
+{
+ "errors": [
+ {
+ "message": "Unexpected Execution Error",
+ "locations": [
+ {
+ "line": 2,
+ "column": 3
+ }
+ ],
+ "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
new file mode 100644
index 00000000000..af087216eda
--- /dev/null
+++ b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Query_With_Connection.snap
@@ -0,0 +1,34 @@
+{
+ "errors": [
+ {
+ "message": "Unexpected Execution Error",
+ "locations": [
+ {
+ "line": 4,
+ "column": 7
+ }
+ ],
+ "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
new file mode 100644
index 00000000000..ba3ef427b57
--- /dev/null
+++ b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/SemanticNonNullTests.Query_With_NullableConnectionNodes.snap
@@ -0,0 +1,17 @@
+{
+ "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
new file mode 100644
index 00000000000..f20ad4eaa9e
--- /dev/null
+++ b/src/HotChocolate/Core/test/Types.Tests/SemanticNonNullTests.cs
@@ -0,0 +1,334 @@
+#nullable enable
+
+using HotChocolate.Execution;
+using HotChocolate.Tests;
+using HotChocolate.Types;
+using HotChocolate.Types.Relay;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace HotChocolate;
+
+public class SemanticNonNullTests
+{
+ [Fact]
+ public async Task Object_Implementing_Node()
+ {
+ await new ServiceCollection()
+ .AddGraphQL()
+ .ModifyOptions(o =>
+ {
+ o.EnableSemanticNonNull = true;
+ o.EnsureAllNodesCanBeResolved = false;
+ })
+ .AddQueryType()
+ .AddGlobalObjectIdentification()
+ .BuildSchemaAsync()
+ .MatchSnapshotAsync();
+ }
+
+ [Fact]
+ public async Task MutationConventions()
+ {
+ await new ServiceCollection()
+ .AddGraphQL()
+ .ModifyOptions(o =>
+ {
+ o.StrictValidation = false;
+ o.EnableSemanticNonNull = true;
+ })
+ .AddMutationConventions()
+ .AddMutationType()
+ .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 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; } = default!;
+ }
+
+ [ObjectType("Query")]
+ public class QueryWithNode
+ {
+ public MyNode GetMyNode() => new(1);
+ }
+
+ [Node]
+ public record MyNode([property: ID] int Id);
+
+ public class Mutation
+ {
+ [UseMutationConvention]
+ [Error]
+ public bool DoSomething() => 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
new file mode 100644
index 00000000000..08e902a8348
--- /dev/null
+++ b/src/HotChocolate/Core/test/Types.Tests/__snapshots__/SemanticNonNullTests.Apply_SemanticNonNull_To_SchemaFirst.snap
@@ -0,0 +1,27 @@
+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
new file mode 100644
index 00000000000..08e902a8348
--- /dev/null
+++ b/src/HotChocolate/Core/test/Types.Tests/__snapshots__/SemanticNonNullTests.Derive_SemanticNonNull_From_CodeFirst.snap
@@ -0,0 +1,27 @@
+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
new file mode 100644
index 00000000000..08e902a8348
--- /dev/null
+++ b/src/HotChocolate/Core/test/Types.Tests/__snapshots__/SemanticNonNullTests.Derive_SemanticNonNull_From_ImplementationFirst.snap
@@ -0,0 +1,27 @@
+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
new file mode 100644
index 00000000000..08e902a8348
--- /dev/null
+++ b/src/HotChocolate/Core/test/Types.Tests/__snapshots__/SemanticNonNullTests.Derive_SemanticNonNull_From_ImplementationFirst_With_GraphQLType_As_String.snap
@@ -0,0 +1,27 @@
+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
new file mode 100644
index 00000000000..08e902a8348
--- /dev/null
+++ b/src/HotChocolate/Core/test/Types.Tests/__snapshots__/SemanticNonNullTests.Derive_SemanticNonNull_From_ImplementationFirst_With_GraphQLType_As_Type.snap
@@ -0,0 +1,27 @@
+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.MutationConventions.snap b/src/HotChocolate/Core/test/Types.Tests/__snapshots__/SemanticNonNullTests.MutationConventions.snap
new file mode 100644
index 00000000000..9441a12814c
--- /dev/null
+++ b/src/HotChocolate/Core/test/Types.Tests/__snapshots__/SemanticNonNullTests.MutationConventions.snap
@@ -0,0 +1,24 @@
+schema {
+ mutation: Mutation
+}
+
+interface Error {
+ message: String @semanticNonNull
+}
+
+type DoSomethingPayload {
+ boolean: Boolean
+ errors: [DoSomethingError] @semanticNonNull(levels: [ 1 ])
+}
+
+type Mutation {
+ doSomething: DoSomethingPayload!
+}
+
+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_Implementing_Node.snap b/src/HotChocolate/Core/test/Types.Tests/__snapshots__/SemanticNonNullTests.Object_Implementing_Node.snap
new file mode 100644
index 00000000000..454e9932455
--- /dev/null
+++ b/src/HotChocolate/Core/test/Types.Tests/__snapshots__/SemanticNonNullTests.Object_Implementing_Node.snap
@@ -0,0 +1,22 @@
+schema {
+ query: Query
+}
+
+"The node interface is implemented by entities that have a global unique identifier."
+interface Node {
+ id: ID!
+}
+
+type MyNode implements Node {
+ id: ID!
+}
+
+type Query {
+ "Fetches an object given its ID."
+ node("ID of the object." id: ID!): Node
+ "Lookup nodes by a list of IDs."
+ nodes("The list of node IDs." ids: [ID!]!): [Node]!
+ myNode: MyNode @semanticNonNull
+}
+
+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
new file mode 100644
index 00000000000..146dc8ea148
--- /dev/null
+++ b/src/HotChocolate/Core/test/Types.Tests/__snapshots__/SemanticNonNullTests.Pagination.snap
@@ -0,0 +1,56 @@
+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