From 54ae2989ebc831d09ca48c54fec6fb187ff07550 Mon Sep 17 00:00:00 2001
From: tobias-tengler <45513122+tobias-tengler@users.noreply.github.com>
Date: Fri, 9 Feb 2024 21:00:07 +0100
Subject: [PATCH 1/4] Add option to disable null-bubbling
---
.../src/Abstractions/WellKnownContextData.cs | 11 +++++++---
.../Pipeline/OperationResolverMiddleware.cs | 5 +++++
.../Core/src/Types/IReadOnlySchemaOptions.cs | 9 +++++++--
.../Core/src/Types/SchemaBuilder.cs | 3 ++-
.../Core/src/Types/SchemaOptions.cs | 6 ++++++
.../DisableNullBubblingTypeInterceptor.cs | 20 +++++++++++++++++++
6 files changed, 48 insertions(+), 6 deletions(-)
create mode 100644 src/HotChocolate/Core/src/Types/Types/Interceptors/DisableNullBubblingTypeInterceptor.cs
diff --git a/src/HotChocolate/Core/src/Abstractions/WellKnownContextData.cs b/src/HotChocolate/Core/src/Abstractions/WellKnownContextData.cs
index 421e741b84f..73bc1d4d456 100644
--- a/src/HotChocolate/Core/src/Abstractions/WellKnownContextData.cs
+++ b/src/HotChocolate/Core/src/Abstractions/WellKnownContextData.cs
@@ -274,17 +274,22 @@ public static class WellKnownContextData
/// The key to access the authorization allowed flag on the member context.
///
public const string AllowAnonymous = "HotChocolate.Authorization.AllowAnonymous";
-
+
///
/// The key to access the true nullability flag on the execution context.
///
public const string EnableTrueNullability = "HotChocolate.Types.EnableTrueNullability";
-
+
+ ///
+ /// The key to access the disable null-bubbling flag on the execution context.
+ ///
+ public const string DisableNullBubbling = "HotChocolate.Types.DisableNullBubbling";
+
///
/// The key to access the tag options object.
///
public const string TagOptions = "HotChocolate.Types.TagOptions";
-
+
///
/// Type key to access the internal schema options.
///
diff --git a/src/HotChocolate/Core/src/Execution/Pipeline/OperationResolverMiddleware.cs b/src/HotChocolate/Core/src/Execution/Pipeline/OperationResolverMiddleware.cs
index 257d98021e3..bf065002ced 100644
--- a/src/HotChocolate/Core/src/Execution/Pipeline/OperationResolverMiddleware.cs
+++ b/src/HotChocolate/Core/src/Execution/Pipeline/OperationResolverMiddleware.cs
@@ -109,6 +109,11 @@ public async ValueTask InvokeAsync(IRequestContext context)
private bool IsNullBubblingEnabled(IRequestContext context, OperationDefinitionNode operationDefinition)
{
+ if (context.Schema.ContextData.ContainsKey(DisableNullBubbling))
+ {
+ return false;
+ }
+
if (!context.Schema.ContextData.ContainsKey(EnableTrueNullability) ||
operationDefinition.Directives.Count == 0)
{
diff --git a/src/HotChocolate/Core/src/Types/IReadOnlySchemaOptions.cs b/src/HotChocolate/Core/src/Types/IReadOnlySchemaOptions.cs
index 1bb10098e6a..c51de86b420 100644
--- a/src/HotChocolate/Core/src/Types/IReadOnlySchemaOptions.cs
+++ b/src/HotChocolate/Core/src/Types/IReadOnlySchemaOptions.cs
@@ -55,7 +55,7 @@ public interface IReadOnlySchemaOptions
/// unreachable from the root types.
///
bool RemoveUnreachableTypes { get; }
-
+
///
/// Defines if unused type system directives shall
/// be removed from the schema.
@@ -97,7 +97,7 @@ public interface IReadOnlySchemaOptions
/// Defines if the order of important middleware components shall be validated.
///
bool ValidatePipelineOrder { get; }
-
+
///
/// Defines if the runtime types of types shall be validated.
///
@@ -181,6 +181,11 @@ public interface IReadOnlySchemaOptions
///
bool EnableTrueNullability { get; }
+ ///
+ /// Specifies whether null-bubbling shall be disabled.
+ ///
+ bool DisableNullBubbling { get; }
+
///
/// Specifies that the @tag directive shall be registered with the type system.
///
diff --git a/src/HotChocolate/Core/src/Types/SchemaBuilder.cs b/src/HotChocolate/Core/src/Types/SchemaBuilder.cs
index 43e3450d759..a2f4c6e12e0 100644
--- a/src/HotChocolate/Core/src/Types/SchemaBuilder.cs
+++ b/src/HotChocolate/Core/src/Types/SchemaBuilder.cs
@@ -39,6 +39,7 @@ public partial class SchemaBuilder : ISchemaBuilder
typeof(CostTypeInterceptor),
typeof(MiddlewareValidationTypeInterceptor),
typeof(EnableTrueNullabilityTypeInterceptor),
+ typeof(DisableNullBubblingTypeInterceptor),
];
private SchemaOptions _options = new();
@@ -263,7 +264,7 @@ public ISchemaBuilder AddType(INamedTypeExtension typeExtension)
_types.Add(_ => TypeReference.Create(typeExtension));
return this;
}
-
+
internal void AddTypeReference(TypeReference typeReference)
{
if (typeReference is null)
diff --git a/src/HotChocolate/Core/src/Types/SchemaOptions.cs b/src/HotChocolate/Core/src/Types/SchemaOptions.cs
index f55f4c8ac4c..63c4f6beec6 100644
--- a/src/HotChocolate/Core/src/Types/SchemaOptions.cs
+++ b/src/HotChocolate/Core/src/Types/SchemaOptions.cs
@@ -211,6 +211,11 @@ public FieldBindingFlags DefaultFieldBindingFlags
///
public bool EnableTrueNullability { get; set; }
+ ///
+ /// Specifies whether null-bubbling shall be disabled.
+ ///
+ public bool DisableNullBubbling { get; set; }
+
///
/// Specifies that the @tag directive shall be registered with the type system.
///
@@ -263,6 +268,7 @@ public static SchemaOptions FromOptions(IReadOnlySchemaOptions options)
MaxAllowedNodeBatchSize = options.MaxAllowedNodeBatchSize,
StripLeadingIFromInterface = options.StripLeadingIFromInterface,
EnableTrueNullability = options.EnableTrueNullability,
+ DisableNullBubbling = options.DisableNullBubbling,
EnableTag = options.EnableTag,
DefaultQueryDependencyInjectionScope = options.DefaultQueryDependencyInjectionScope,
DefaultMutationDependencyInjectionScope = options.DefaultMutationDependencyInjectionScope,
diff --git a/src/HotChocolate/Core/src/Types/Types/Interceptors/DisableNullBubblingTypeInterceptor.cs b/src/HotChocolate/Core/src/Types/Types/Interceptors/DisableNullBubblingTypeInterceptor.cs
new file mode 100644
index 00000000000..f220b15f5a3
--- /dev/null
+++ b/src/HotChocolate/Core/src/Types/Types/Interceptors/DisableNullBubblingTypeInterceptor.cs
@@ -0,0 +1,20 @@
+#nullable enable
+using HotChocolate.Configuration;
+using HotChocolate.Types.Descriptors;
+using HotChocolate.Types.Descriptors.Definitions;
+
+namespace HotChocolate.Types.Interceptors;
+
+internal sealed class DisableNullBubblingTypeInterceptor : TypeInterceptor
+{
+ internal override bool IsEnabled(IDescriptorContext context)
+ => context.Options.DisableNullBubbling;
+
+ public override void OnAfterInitialize(ITypeDiscoveryContext discoveryContext, DefinitionBase definition)
+ {
+ if (definition is SchemaTypeDefinition schemaDef)
+ {
+ schemaDef.ContextData[WellKnownContextData.DisableNullBubbling] = true;
+ }
+ }
+}
From 0a94e864c2330efb509acb8ec2349c02cd107a9a Mon Sep 17 00:00:00 2001
From: tobias-tengler <45513122+tobias-tengler@users.noreply.github.com>
Date: Sat, 10 Feb 2024 11:31:40 +0100
Subject: [PATCH 2/4] Add GraphQL-Disable-NullBubbling header
---
.../DefaultHttpRequestInterceptor.cs | 5 ++++
.../DefaultSocketSessionInterceptor.cs | 5 ++++
.../Extensions/HttpContextExtensions.cs | 13 +++++++++
.../src/AspNetCore/HttpHeaderKeys.cs | 2 ++
.../src/AspNetCore/HttpHeaderValues.cs | 2 ++
.../HttpRequestHeadersExtensions.cs | 27 +++++++++++++++++--
.../src/Abstractions/WellKnownContextData.cs | 9 ++-----
.../Pipeline/OperationResolverMiddleware.cs | 3 ++-
.../Core/src/Types/IReadOnlySchemaOptions.cs | 5 ----
.../Core/src/Types/SchemaBuilder.cs | 1 -
.../Core/src/Types/SchemaOptions.cs | 6 -----
.../DisableNullBubblingTypeInterceptor.cs | 20 --------------
12 files changed, 56 insertions(+), 42 deletions(-)
delete mode 100644 src/HotChocolate/Core/src/Types/Types/Interceptors/DisableNullBubblingTypeInterceptor.cs
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/DefaultHttpRequestInterceptor.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/DefaultHttpRequestInterceptor.cs
index 0f3701f7338..7928bb943fb 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/DefaultHttpRequestInterceptor.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore/DefaultHttpRequestInterceptor.cs
@@ -34,6 +34,11 @@ public class DefaultHttpRequestInterceptor : IHttpRequestInterceptor
requestBuilder.TryAddGlobalState(WellKnownContextData.IncludeQueryPlan, true);
}
+ if (context.IsNullBubblingDisabled())
+ {
+ requestBuilder.TryAddGlobalState(WellKnownContextData.DisableNullBubbling, true);
+ }
+
return default;
}
}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/DefaultSocketSessionInterceptor.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/DefaultSocketSessionInterceptor.cs
index 486f0556c7a..9d746f40c81 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/DefaultSocketSessionInterceptor.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore/DefaultSocketSessionInterceptor.cs
@@ -44,6 +44,11 @@ public class DefaultSocketSessionInterceptor : ISocketSessionInterceptor
requestBuilder.TryAddGlobalState(IncludeQueryPlan, true);
}
+ if (context.IsNullBubblingDisabled())
+ {
+ requestBuilder.TryAddGlobalState(WellKnownContextData.DisableNullBubbling, true);
+ }
+
return default;
}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HttpContextExtensions.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HttpContextExtensions.cs
index 62db2dbdfce..314b1bc11ae 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HttpContextExtensions.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HttpContextExtensions.cs
@@ -35,4 +35,17 @@ public static bool IncludeQueryPlan(this HttpContext context)
return false;
}
+
+ public static bool IsNullBubblingDisabled(this HttpContext context)
+ {
+ var headers = context.Request.Headers;
+
+ if (headers.TryGetValue(HttpHeaderKeys.DisableNullBubbling, out var values) &&
+ values.Any(v => v == HttpHeaderValues.DisableNullBubbling))
+ {
+ return true;
+ }
+
+ return false;
+ }
}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/HttpHeaderKeys.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/HttpHeaderKeys.cs
index d946a1a0dee..ad9d2b55b2f 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/HttpHeaderKeys.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore/HttpHeaderKeys.cs
@@ -11,4 +11,6 @@ internal static class HttpHeaderKeys
public const string CacheControl = "Cache-Control";
public const string Preflight = "GraphQL-Preflight";
+
+ public const string DisableNullBubbling = "GraphQL-Disable-NullBubbling";
}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/HttpHeaderValues.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/HttpHeaderValues.cs
index 02bf31125f3..5f29e488e62 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/HttpHeaderValues.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore/HttpHeaderValues.cs
@@ -6,5 +6,7 @@ internal static class HttpHeaderValues
public const string IncludeQueryPlan = "1";
+ public const string DisableNullBubbling = "1";
+
public const string NoCache = "no-cache";
}
diff --git a/src/HotChocolate/AspNetCore/src/Transport.Http/HttpRequestHeadersExtensions.cs b/src/HotChocolate/AspNetCore/src/Transport.Http/HttpRequestHeadersExtensions.cs
index 74abc5616dd..1a13eada177 100644
--- a/src/HotChocolate/AspNetCore/src/Transport.Http/HttpRequestHeadersExtensions.cs
+++ b/src/HotChocolate/AspNetCore/src/Transport.Http/HttpRequestHeadersExtensions.cs
@@ -29,5 +29,28 @@ public static HttpRequestHeaders AddGraphQLPreflight(this HttpRequestHeaders hea
headers.Add("GraphQL-Preflight", "1");
return headers;
- }
-}
\ No newline at end of file
+ }
+
+ ///
+ /// Adds the GraphQL-Disable-NullBubbling header to the request.
+ ///
+ ///
+ /// The to add the header to.
+ ///
+ ///
+ /// Returns the for configuration chaining.
+ ///
+ ///
+ /// is .
+ ///
+ public static HttpRequestHeaders AddGraphQLDisableNullBubbling(this HttpRequestHeaders headers)
+ {
+ if (headers == null)
+ {
+ throw new ArgumentNullException(nameof(headers));
+ }
+
+ headers.Add("GraphQL-Disable-NullBubbling", "1");
+ return headers;
+ }
+}
diff --git a/src/HotChocolate/Core/src/Abstractions/WellKnownContextData.cs b/src/HotChocolate/Core/src/Abstractions/WellKnownContextData.cs
index 73bc1d4d456..2a5ddd8ed82 100644
--- a/src/HotChocolate/Core/src/Abstractions/WellKnownContextData.cs
+++ b/src/HotChocolate/Core/src/Abstractions/WellKnownContextData.cs
@@ -169,11 +169,6 @@ public static class WellKnownContextData
///
public const string NodeResolver = "HotChocolate.Relay.Node.Resolver";
- ///
- /// The key to check if relay support is enabled.
- ///
- public const string IsRelaySupportEnabled = "HotChocolate.Relay.IsEnabled";
-
///
/// The key to check if the global identification spec is enabled.
///
@@ -281,9 +276,9 @@ public static class WellKnownContextData
public const string EnableTrueNullability = "HotChocolate.Types.EnableTrueNullability";
///
- /// The key to access the disable null-bubbling flag on the execution context.
+ /// Disables null-bubbling for the current request.
///
- public const string DisableNullBubbling = "HotChocolate.Types.DisableNullBubbling";
+ public const string DisableNullBubbling = "HotChocolate.Execution.DisableNullBubbling";
///
/// The key to access the tag options object.
diff --git a/src/HotChocolate/Core/src/Execution/Pipeline/OperationResolverMiddleware.cs b/src/HotChocolate/Core/src/Execution/Pipeline/OperationResolverMiddleware.cs
index bf065002ced..077ffa71107 100644
--- a/src/HotChocolate/Core/src/Execution/Pipeline/OperationResolverMiddleware.cs
+++ b/src/HotChocolate/Core/src/Execution/Pipeline/OperationResolverMiddleware.cs
@@ -109,7 +109,8 @@ public async ValueTask InvokeAsync(IRequestContext context)
private bool IsNullBubblingEnabled(IRequestContext context, OperationDefinitionNode operationDefinition)
{
- if (context.Schema.ContextData.ContainsKey(DisableNullBubbling))
+ if (context.ContextData.TryGetValue(DisableNullBubbling, out var disableNullBubbling)
+ && disableNullBubbling is true)
{
return false;
}
diff --git a/src/HotChocolate/Core/src/Types/IReadOnlySchemaOptions.cs b/src/HotChocolate/Core/src/Types/IReadOnlySchemaOptions.cs
index c51de86b420..16895e6ff8a 100644
--- a/src/HotChocolate/Core/src/Types/IReadOnlySchemaOptions.cs
+++ b/src/HotChocolate/Core/src/Types/IReadOnlySchemaOptions.cs
@@ -181,11 +181,6 @@ public interface IReadOnlySchemaOptions
///
bool EnableTrueNullability { get; }
- ///
- /// Specifies whether null-bubbling shall be disabled.
- ///
- bool DisableNullBubbling { get; }
-
///
/// Specifies that the @tag directive shall be registered with the type system.
///
diff --git a/src/HotChocolate/Core/src/Types/SchemaBuilder.cs b/src/HotChocolate/Core/src/Types/SchemaBuilder.cs
index a2f4c6e12e0..0612dbba5fb 100644
--- a/src/HotChocolate/Core/src/Types/SchemaBuilder.cs
+++ b/src/HotChocolate/Core/src/Types/SchemaBuilder.cs
@@ -39,7 +39,6 @@ public partial class SchemaBuilder : ISchemaBuilder
typeof(CostTypeInterceptor),
typeof(MiddlewareValidationTypeInterceptor),
typeof(EnableTrueNullabilityTypeInterceptor),
- typeof(DisableNullBubblingTypeInterceptor),
];
private SchemaOptions _options = new();
diff --git a/src/HotChocolate/Core/src/Types/SchemaOptions.cs b/src/HotChocolate/Core/src/Types/SchemaOptions.cs
index 63c4f6beec6..f55f4c8ac4c 100644
--- a/src/HotChocolate/Core/src/Types/SchemaOptions.cs
+++ b/src/HotChocolate/Core/src/Types/SchemaOptions.cs
@@ -211,11 +211,6 @@ public FieldBindingFlags DefaultFieldBindingFlags
///
public bool EnableTrueNullability { get; set; }
- ///
- /// Specifies whether null-bubbling shall be disabled.
- ///
- public bool DisableNullBubbling { get; set; }
-
///
/// Specifies that the @tag directive shall be registered with the type system.
///
@@ -268,7 +263,6 @@ public static SchemaOptions FromOptions(IReadOnlySchemaOptions options)
MaxAllowedNodeBatchSize = options.MaxAllowedNodeBatchSize,
StripLeadingIFromInterface = options.StripLeadingIFromInterface,
EnableTrueNullability = options.EnableTrueNullability,
- DisableNullBubbling = options.DisableNullBubbling,
EnableTag = options.EnableTag,
DefaultQueryDependencyInjectionScope = options.DefaultQueryDependencyInjectionScope,
DefaultMutationDependencyInjectionScope = options.DefaultMutationDependencyInjectionScope,
diff --git a/src/HotChocolate/Core/src/Types/Types/Interceptors/DisableNullBubblingTypeInterceptor.cs b/src/HotChocolate/Core/src/Types/Types/Interceptors/DisableNullBubblingTypeInterceptor.cs
deleted file mode 100644
index f220b15f5a3..00000000000
--- a/src/HotChocolate/Core/src/Types/Types/Interceptors/DisableNullBubblingTypeInterceptor.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-#nullable enable
-using HotChocolate.Configuration;
-using HotChocolate.Types.Descriptors;
-using HotChocolate.Types.Descriptors.Definitions;
-
-namespace HotChocolate.Types.Interceptors;
-
-internal sealed class DisableNullBubblingTypeInterceptor : TypeInterceptor
-{
- internal override bool IsEnabled(IDescriptorContext context)
- => context.Options.DisableNullBubbling;
-
- public override void OnAfterInitialize(ITypeDiscoveryContext discoveryContext, DefinitionBase definition)
- {
- if (definition is SchemaTypeDefinition schemaDef)
- {
- schemaDef.ContextData[WellKnownContextData.DisableNullBubbling] = true;
- }
- }
-}
From def180cf02e1cd1a13c988dea18c27d548234fe3 Mon Sep 17 00:00:00 2001
From: tobias-tengler <45513122+tobias-tengler@users.noreply.github.com>
Date: Sat, 10 Feb 2024 18:37:36 +0100
Subject: [PATCH 3/4] Add simple test
---
.../Execution.Tests/TrueNullabilityTests.cs | 58 ++++++++++++++-----
...rror_Query_With_NullBubbling_Disabled.snap | 26 +++++++++
2 files changed, 68 insertions(+), 16 deletions(-)
create mode 100644 src/HotChocolate/Core/test/Execution.Tests/__snapshots__/TrueNullabilityTests.Error_Query_With_NullBubbling_Disabled.snap
diff --git a/src/HotChocolate/Core/test/Execution.Tests/TrueNullabilityTests.cs b/src/HotChocolate/Core/test/Execution.Tests/TrueNullabilityTests.cs
index 512ce55b61d..09ded9bb63b 100644
--- a/src/HotChocolate/Core/test/Execution.Tests/TrueNullabilityTests.cs
+++ b/src/HotChocolate/Core/test/Execution.Tests/TrueNullabilityTests.cs
@@ -15,10 +15,10 @@ public async Task Schema_Without_TrueNullability()
.AddQueryType()
.ModifyOptions(o => o.EnableTrueNullability = false)
.BuildSchemaAsync();
-
+
schema.MatchSnapshot();
}
-
+
[Fact]
public async Task Schema_With_TrueNullability()
{
@@ -28,10 +28,10 @@ public async Task Schema_With_TrueNullability()
.AddQueryType()
.ModifyOptions(o => o.EnableTrueNullability = true)
.BuildSchemaAsync();
-
+
schema.MatchSnapshot();
}
-
+
[Fact]
public async Task Error_Query_With_TrueNullability_And_NullBubbling_Enabled_By_Default()
{
@@ -51,10 +51,10 @@ public async Task Error_Query_With_TrueNullability_And_NullBubbling_Enabled_By_D
}
}
""");
-
+
response.MatchSnapshot();
}
-
+
[Fact]
public async Task Error_Query_With_TrueNullability_And_NullBubbling_Enabled()
{
@@ -74,10 +74,10 @@ public async Task Error_Query_With_TrueNullability_And_NullBubbling_Enabled()
}
}
""");
-
+
response.MatchSnapshot();
}
-
+
[Fact]
public async Task Error_Query_With_TrueNullability_And_NullBubbling_Disabled()
{
@@ -97,10 +97,10 @@ public async Task Error_Query_With_TrueNullability_And_NullBubbling_Disabled()
}
}
""");
-
+
response.MatchSnapshot();
}
-
+
[Fact]
public async Task Error_Query_With_TrueNullability_And_NullBubbling_Disabled_With_Variable()
{
@@ -124,24 +124,50 @@ public async Task Error_Query_With_TrueNullability_And_NullBubbling_Disabled_Wit
""")
.SetVariableValue("enable", false)
.Create());
-
+
+ response.MatchSnapshot();
+ }
+
+ [Fact]
+ public async Task Error_Query_With_NullBubbling_Disabled()
+ {
+ var request = QueryRequestBuilder.New()
+ .SetQuery("""
+ query {
+ book {
+ name
+ author {
+ name
+ }
+ }
+ }
+ """)
+ .TryAddGlobalState(WellKnownContextData.DisableNullBubbling, true)
+ .Create();
+
+ var response =
+ await new ServiceCollection()
+ .AddGraphQLServer()
+ .AddQueryType()
+ .ExecuteRequestAsync(request);
+
response.MatchSnapshot();
}
-
+
public class Query
{
public Book? GetBook() => new();
}
-
+
public class Book
{
public string Name => "Some book!";
public Author Author => new();
}
-
+
public class Author
{
public string Name => throw new Exception();
- }
-}
\ No newline at end of file
+ }
+}
diff --git a/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/TrueNullabilityTests.Error_Query_With_NullBubbling_Disabled.snap b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/TrueNullabilityTests.Error_Query_With_NullBubbling_Disabled.snap
new file mode 100644
index 00000000000..5f1271ea8b6
--- /dev/null
+++ b/src/HotChocolate/Core/test/Execution.Tests/__snapshots__/TrueNullabilityTests.Error_Query_With_NullBubbling_Disabled.snap
@@ -0,0 +1,26 @@
+{
+ "errors": [
+ {
+ "message": "Unexpected Execution Error",
+ "locations": [
+ {
+ "line": 5,
+ "column": 13
+ }
+ ],
+ "path": [
+ "book",
+ "author",
+ "name"
+ ]
+ }
+ ],
+ "data": {
+ "book": {
+ "name": "Some book!",
+ "author": {
+ "name": null
+ }
+ }
+ }
+}
\ No newline at end of file
From 69e564d605e589fffc0c7a30f957b11df7730837 Mon Sep 17 00:00:00 2001
From: tobias-tengler <45513122+tobias-tengler@users.noreply.github.com>
Date: Fri, 22 Mar 2024 19:49:25 +0100
Subject: [PATCH 4/4] Add AllowDisablingNullBubbling option
---
.../Options/IRequestExecutorOptionsAccessor.cs | 12 +++++++++---
.../src/Execution/Options/RequestExecutorOptions.cs | 9 +++++++--
.../Pipeline/OperationResolverMiddleware.cs | 12 +++++++++---
.../test/Execution.Tests/TrueNullabilityTests.cs | 1 +
4 files changed, 26 insertions(+), 8 deletions(-)
diff --git a/src/HotChocolate/Core/src/Execution/Options/IRequestExecutorOptionsAccessor.cs b/src/HotChocolate/Core/src/Execution/Options/IRequestExecutorOptionsAccessor.cs
index 1207b425206..2f7738e5557 100644
--- a/src/HotChocolate/Core/src/Execution/Options/IRequestExecutorOptionsAccessor.cs
+++ b/src/HotChocolate/Core/src/Execution/Options/IRequestExecutorOptionsAccessor.cs
@@ -8,6 +8,12 @@ namespace HotChocolate.Execution.Options;
///
public interface IRequestExecutorOptionsAccessor
: IErrorHandlerOptionsAccessor
- , IRequestTimeoutOptionsAccessor
- , IComplexityAnalyzerOptionsAccessor
- , IPersistedQueryOptionsAccessor;
+ , IRequestTimeoutOptionsAccessor
+ , IComplexityAnalyzerOptionsAccessor
+ , IPersistedQueryOptionsAccessor
+{
+ ///
+ /// Determine whether null-bubbling can be disabled on a per-request basis.
+ ///
+ bool AllowDisablingNullBubbling { get; }
+}
diff --git a/src/HotChocolate/Core/src/Execution/Options/RequestExecutorOptions.cs b/src/HotChocolate/Core/src/Execution/Options/RequestExecutorOptions.cs
index 229128b2361..b37dc655ead 100644
--- a/src/HotChocolate/Core/src/Execution/Options/RequestExecutorOptions.cs
+++ b/src/HotChocolate/Core/src/Execution/Options/RequestExecutorOptions.cs
@@ -72,9 +72,14 @@ public IError OnlyPersistedQueriesAreAllowedError
get => _onlyPersistedQueriesAreAllowedError;
set
{
- _onlyPersistedQueriesAreAllowedError = value
- ?? throw new ArgumentNullException(
+ _onlyPersistedQueriesAreAllowedError = value ??
+ throw new ArgumentNullException(
nameof(OnlyPersistedQueriesAreAllowedError));
}
}
+
+ ///
+ /// Determine whether null-bubbling can be disabled on a per-request basis.
+ ///
+ public bool AllowDisablingNullBubbling { get; set; } = false;
}
diff --git a/src/HotChocolate/Core/src/Execution/Pipeline/OperationResolverMiddleware.cs b/src/HotChocolate/Core/src/Execution/Pipeline/OperationResolverMiddleware.cs
index 077ffa71107..22513c4dee5 100644
--- a/src/HotChocolate/Core/src/Execution/Pipeline/OperationResolverMiddleware.cs
+++ b/src/HotChocolate/Core/src/Execution/Pipeline/OperationResolverMiddleware.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
+using HotChocolate.Execution.Options;
using HotChocolate.Execution.Processing;
using HotChocolate.Language;
using HotChocolate.Types;
@@ -19,6 +20,7 @@ internal sealed class OperationResolverMiddleware
{
private readonly RequestDelegate _next;
private readonly ObjectPool _operationCompilerPool;
+ private readonly IRequestExecutorOptionsAccessor _options;
private readonly VariableCoercionHelper _coercionHelper;
private readonly IReadOnlyList? _optimizers;
@@ -26,6 +28,7 @@ internal sealed class OperationResolverMiddleware
RequestDelegate next,
ObjectPool operationCompilerPool,
IEnumerable optimizers,
+ IRequestExecutorOptionsAccessor options,
VariableCoercionHelper coercionHelper)
{
if (optimizers is null)
@@ -37,6 +40,8 @@ internal sealed class OperationResolverMiddleware
throw new ArgumentNullException(nameof(next));
_operationCompilerPool = operationCompilerPool ??
throw new ArgumentNullException(nameof(operationCompilerPool));
+ _options = options ??
+ throw new ArgumentNullException(nameof(options));
_coercionHelper = coercionHelper ??
throw new ArgumentNullException(nameof(coercionHelper));
_optimizers = optimizers.ToArray();
@@ -109,8 +114,7 @@ public async ValueTask InvokeAsync(IRequestContext context)
private bool IsNullBubblingEnabled(IRequestContext context, OperationDefinitionNode operationDefinition)
{
- if (context.ContextData.TryGetValue(DisableNullBubbling, out var disableNullBubbling)
- && disableNullBubbling is true)
+ if (_options.AllowDisablingNullBubbling && context.ContextData.ContainsKey(DisableNullBubbling))
{
return false;
}
@@ -175,12 +179,14 @@ public static RequestCoreMiddleware Create()
var operationCompilerPool = core.Services.GetRequiredService>();
var optimizers1 = core.Services.GetRequiredService>();
var optimizers2 = core.SchemaServices.GetRequiredService>();
+ var options = core.SchemaServices.GetRequiredService();
var coercionHelper = core.Services.GetRequiredService();
var middleware = new OperationResolverMiddleware(
next,
operationCompilerPool,
optimizers1.Concat(optimizers2),
+ options,
coercionHelper);
return context => middleware.InvokeAsync(context);
};
-}
\ No newline at end of file
+}
diff --git a/src/HotChocolate/Core/test/Execution.Tests/TrueNullabilityTests.cs b/src/HotChocolate/Core/test/Execution.Tests/TrueNullabilityTests.cs
index 09ded9bb63b..5e4b1cebc32 100644
--- a/src/HotChocolate/Core/test/Execution.Tests/TrueNullabilityTests.cs
+++ b/src/HotChocolate/Core/test/Execution.Tests/TrueNullabilityTests.cs
@@ -148,6 +148,7 @@ public async Task Error_Query_With_NullBubbling_Disabled()
var response =
await new ServiceCollection()
.AddGraphQLServer()
+ .ModifyRequestOptions(options => options.AllowDisablingNullBubbling = true)
.AddQueryType()
.ExecuteRequestAsync(request);