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