Skip to content

Commit

Permalink
true nullability
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelstaib committed Oct 2, 2023
1 parent b1dc205 commit fade944
Show file tree
Hide file tree
Showing 24 changed files with 548 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -274,4 +274,9 @@ public static class WellKnownContextData
/// The key to access the authorization allowed flag on the member context.
/// </summary>
public const string AllowAnonymous = "HotChocolate.Authorization.AllowAnonymous";

/// <summary>
/// The key to access the true nullability flag on the execution context.
/// </summary>
public const string EnableTrueNullability = "HotChocolate.Types.EnableTrueNullability";
}
4 changes: 4 additions & 0 deletions src/HotChocolate/Core/src/Abstractions/WellKnownDirectives.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,8 @@ public static class WellKnownDirectives
/// The name of the @tag argument name.
/// </summary>
public const string Name = "name";

public const string NullBubbling = "nullBubbling";

public const string Enable = "enable";
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,38 @@
using HotChocolate.Execution.Processing;
using HotChocolate.Language;
using HotChocolate.Types;
using HotChocolate.Utilities;
using Microsoft.Extensions.ObjectPool;
using static HotChocolate.WellKnownDirectives;
using static HotChocolate.Execution.Pipeline.PipelineTools;
using static HotChocolate.WellKnownContextData;

namespace HotChocolate.Execution.Pipeline;

internal sealed class OperationResolverMiddleware
{
private readonly RequestDelegate _next;
private readonly ObjectPool<OperationCompiler> _operationCompilerPool;
private readonly VariableCoercionHelper _coercionHelper;
private readonly IReadOnlyList<IOperationCompilerOptimizer>? _optimizers;

public OperationResolverMiddleware(
RequestDelegate next,
ObjectPool<OperationCompiler> operationCompilerPool,
IEnumerable<IOperationCompilerOptimizer> optimizers)
IEnumerable<IOperationCompilerOptimizer> optimizers,
VariableCoercionHelper coercionHelper)
{
if (optimizers is null)
{
throw new ArgumentNullException(nameof(optimizers));
}

_next = next ?? throw new ArgumentNullException(nameof(next));
_operationCompilerPool = operationCompilerPool;
_next = next ??
throw new ArgumentNullException(nameof(next));
_operationCompilerPool = operationCompilerPool ??
throw new ArgumentNullException(nameof(operationCompilerPool));
_coercionHelper = coercionHelper ??
throw new ArgumentNullException(nameof(coercionHelper));
_optimizers = optimizers.ToArray();
}

Expand Down Expand Up @@ -78,7 +88,8 @@ private IOperation CompileOperation(
operationType,
context.Document!,
context.Schema,
_optimizers);
_optimizers,
IsNullBubblingEnabled(context, operationDefinition));
_operationCompilerPool.Return(compiler);
return operation;
}
Expand All @@ -93,4 +104,73 @@ private IOperation CompileOperation(
OperationType.Subscription => schema.SubscriptionType,
_ => throw ThrowHelper.RootTypeNotSupported(operationType)
};
}

private bool IsNullBubblingEnabled(IRequestContext context, OperationDefinitionNode operationDefinition)
{
if (!context.Schema.ContextData.ContainsKey(EnableTrueNullability) ||
operationDefinition.Directives.Count == 0)
{
return true;
}

var enabled = true;

for (var i = 0; i < operationDefinition.Directives.Count; i++)
{
var directive = operationDefinition.Directives[i];

if (!directive.Name.Value.EqualsOrdinal(NullBubbling))
{
continue;
}

for (var j = 0; j < directive.Arguments.Count; j++)
{
var argument = directive.Arguments[j];

if (argument.Name.Value.EqualsOrdinal(Enable))
{
if (argument.Value is BooleanValueNode b)
{
enabled = b.Value;
break;
}

if (argument.Value is VariableNode v)
{
enabled = CoerceVariable(context, operationDefinition, v);
break;
}

// TOOD : Move to ErrorHelper
var errorBuilder = ErrorBuilder.New();

if (argument.Value.Location is not null)
{
errorBuilder.AddLocation(
argument.Value.Location.Line,
argument.Value.Location.Column);
}

errorBuilder.SetSyntaxNode(argument.Value);
errorBuilder.SetMessage("Only boolean values are allowed here.");

throw new GraphQLException(errorBuilder.Build());
}
}

break;
}

return enabled;
}

private bool CoerceVariable(
IRequestContext context,
OperationDefinitionNode operationDefinition,
VariableNode variable)
{
var variables = CoerceVariables(context, _coercionHelper, operationDefinition.VariableDefinitions);
return variables.GetVariable<bool>(variable.Name.Value);
}
}
6 changes: 4 additions & 2 deletions src/HotChocolate/Core/src/Execution/Pipeline/PipelineTools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,20 @@ public static string CreateCacheId(
string? operationName)
=> CreateCacheId(context, CreateOperationId(documentId, operationName));

public static void CoerceVariables(
public static IVariableValueCollection CoerceVariables(
IRequestContext context,
VariableCoercionHelper coercionHelper,
IReadOnlyList<VariableDefinitionNode> variableDefinitions)
{
if (context.Variables is not null)
{
return;
return context.Variables;
}

if (variableDefinitions.Count == 0)
{
context.Variables = _noVariables;
return _noVariables;
}
else
{
Expand All @@ -52,6 +53,7 @@ public static void CoerceVariables(
coercedValues);

context.Variables = new VariableValueCollection(coercedValues);
return context.Variables;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,11 @@ namespace HotChocolate.Execution.Processing;

public sealed partial class OperationCompiler
{
internal sealed class CompilerContext
internal sealed class CompilerContext(ISchema schema, DocumentNode document, bool disableNullBubbling)
{
public CompilerContext(ISchema schema, DocumentNode document)
{
Schema = schema;
Document = document;
}

public ISchema Schema { get; }
public ISchema Schema { get; } = schema;

public DocumentNode Document { get; }
public DocumentNode Document { get; } = document;

public ObjectType Type { get; private set; } = default!;

Expand All @@ -35,6 +29,8 @@ public CompilerContext(ISchema schema, DocumentNode document)

public IImmutableList<ISelectionSetOptimizer> Optimizers { get; private set; } =
ImmutableList<ISelectionSetOptimizer>.Empty;

public bool EnableNullBubbling { get; } = disableNullBubbling;

public void Initialize(
ObjectType type,
Expand Down
55 changes: 31 additions & 24 deletions src/HotChocolate/Core/src/Execution/Processing/OperationCompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public sealed partial class OperationCompiler
{
private static readonly ImmutableList<ISelectionSetOptimizer> _emptyOptimizers =
ImmutableList<ISelectionSetOptimizer>.Empty;

private readonly InputParser _parser;
private readonly CreateFieldPipeline _createFieldPipeline;
private readonly Stack<BacklogItem> _backlog = new();
Expand Down Expand Up @@ -65,7 +66,8 @@ public IOperation Compile(
ObjectType operationType,
DocumentNode document,
ISchema schema,
IReadOnlyList<IOperationCompilerOptimizer>? optimizers = null)
IReadOnlyList<IOperationCompilerOptimizer>? optimizers = null,
bool enableNullBubbling = true)
{
if (string.IsNullOrEmpty(operationId))
{
Expand Down Expand Up @@ -112,7 +114,7 @@ public IOperation Compile(
var variants = GetOrCreateSelectionVariants(id);
SelectionSetInfo[] infos = { new(operationDefinition.SelectionSet, 0) };

var context = new CompilerContext(schema, document);
var context = new CompilerContext(schema, document, enableNullBubbling);
context.Initialize(operationType, variants, infos, rootPath, rootOptimizers);
CompileSelectionSet(context);

Expand Down Expand Up @@ -378,19 +380,19 @@ private void CompleteSelectionSet(CompilerContext context)
// For now we only allow streams on lists of composite types.
if (selection.SyntaxNode.IsStreamable())
{
var streamDirective = selection.SyntaxNode.GetStreamDirectiveNode();
var nullValue = NullValueNode.Default;
var ifValue = streamDirective?.GetIfArgumentValueOrDefault() ?? nullValue;
long ifConditionFlags = 0;

if (ifValue.Kind is not SyntaxKind.NullValue)
{
var ifCondition = new IncludeCondition(ifValue, nullValue);
ifConditionFlags = GetSelectionIncludeCondition(ifCondition, 0);
}

selection.MarkAsStream(ifConditionFlags);
_hasIncrementalParts = true;
var streamDirective = selection.SyntaxNode.GetStreamDirectiveNode();
var nullValue = NullValueNode.Default;
var ifValue = streamDirective?.GetIfArgumentValueOrDefault() ?? nullValue;
long ifConditionFlags = 0;

if (ifValue.Kind is not SyntaxKind.NullValue)
{
var ifCondition = new IncludeCondition(ifValue, nullValue);
ifConditionFlags = GetSelectionIncludeCondition(ifCondition, 0);
}

selection.MarkAsStream(ifConditionFlags);
_hasIncrementalParts = true;
}
}

Expand Down Expand Up @@ -449,21 +451,21 @@ private void ResolveFields(
case SyntaxKind.Field:
ResolveField(
context,
(FieldNode)selection,
(FieldNode) selection,
includeCondition);
break;

case SyntaxKind.InlineFragment:
ResolveInlineFragment(
context,
(InlineFragmentNode)selection,
(InlineFragmentNode) selection,
includeCondition);
break;

case SyntaxKind.FragmentSpread:
ResolveFragmentSpread(
context,
(FragmentSpreadNode)selection,
(FragmentSpreadNode) selection,
includeCondition);
break;
}
Expand All @@ -481,7 +483,9 @@ private void ResolveField(

if (context.Type.Fields.TryGetField(fieldName, out var field))
{
var fieldType = field.Type.RewriteNullability(selection.Required);
var fieldType = context.EnableNullBubbling
? field.Type.RewriteNullability(selection.Required)
: field.Type.RewriteToNullableType();

if (context.Fields.TryGetValue(responseName, out var preparedSelection))
{
Expand Down Expand Up @@ -516,7 +520,9 @@ selection.SelectionSet is not null
responseName: responseName,
isParallelExecutable: field.IsParallelExecutable,
arguments: CoerceArgumentValues(field, selection, responseName),
includeConditions: includeCondition == 0 ? null : new[] { includeCondition });
includeConditions: includeCondition == 0
? null
: new[] { includeCondition });

context.Fields.Add(responseName, preparedSelection);

Expand Down Expand Up @@ -586,6 +592,7 @@ private void ResolveFragment(
var ifValue = deferDirective?.GetIfArgumentValueOrDefault() ?? nullValue;

long ifConditionFlags = 0;

if (ifValue.Kind is not SyntaxKind.NullValue)
{
var ifCondition = new IncludeCondition(ifValue, nullValue);
Expand Down Expand Up @@ -637,8 +644,8 @@ private static bool DoesTypeApply(IType typeCondition, IObjectType current)
=> typeCondition.Kind switch
{
TypeKind.Object => ReferenceEquals(typeCondition, current),
TypeKind.Interface => current.IsImplementing((InterfaceType)typeCondition),
TypeKind.Union => ((UnionType)typeCondition).Types.ContainsKey(current.Name),
TypeKind.Interface => current.IsImplementing((InterfaceType) typeCondition),
TypeKind.Union => ((UnionType) typeCondition).Types.ContainsKey(current.Name),
_ => false
};

Expand Down Expand Up @@ -789,7 +796,7 @@ private CompilerContext RentContext(CompilerContext context)
{
if (_deferContext is null)
{
return new CompilerContext(context.Schema, context.Document);
return new CompilerContext(context.Schema, context.Document, context.EnableNullBubbling);
}

var temp = _deferContext;
Expand Down Expand Up @@ -862,4 +869,4 @@ public override bool Equals(object? obj)
public override int GetHashCode()
=> HashCode.Combine(SelectionSet, Path);
}
}
}
Loading

0 comments on commit fade944

Please sign in to comment.