Skip to content

Commit

Permalink
Fixed node resolver with entities (#5429)
Browse files Browse the repository at this point in the history
  • Loading branch information
PascalSenn committed Sep 25, 2022
1 parent 698a58b commit cb9265f
Show file tree
Hide file tree
Showing 20 changed files with 650 additions and 352 deletions.
4 changes: 1 addition & 3 deletions src/HotChocolate/Core/src/Abstractions/ExtensionData.cs
Expand Up @@ -8,7 +8,7 @@

namespace HotChocolate;

public class ExtensionData
public sealed class ExtensionData
: IDictionary<string, object?>
, IReadOnlyDictionary<string, object?>
{
Expand Down Expand Up @@ -231,6 +231,4 @@ IEnumerator IEnumerable.GetEnumerator()
return GetEnumerator();
}
}

public static readonly ExtensionData Empty = new();
}
10 changes: 10 additions & 0 deletions src/HotChocolate/Core/src/Abstractions/WellKnownContextData.cs
Expand Up @@ -197,4 +197,14 @@ public static class WellKnownContextData
/// The key to get the IdValue object from the context data.
/// </summary>
public const string IdValue = "HotChocolate.Relay.Node.Id.Value";

/// <summary>
/// The key to get check if a field is the node field.
/// </summary>
public const string IsNodeField = "HotChocolate.Relay.Node.IsNodeField";

/// <summary>
/// The key to get check if a field is the nodes field.
/// </summary>
public const string IsNodesField = "HotChocolate.Relay.Node.IsNodeField";
}
Expand Up @@ -5,6 +5,7 @@
using HotChocolate.Types;
using HotChocolate.Utilities;
using static HotChocolate.Execution.Properties.Resources;
using NameUtils = HotChocolate.Utilities.NameUtils;

namespace HotChocolate.Execution.Processing;

Expand Down Expand Up @@ -126,7 +127,7 @@ public void AddSelection(string responseName, Selection newSelection)
/// </exception>
public void ReplaceSelection(string responseName, Selection newSelection)
{
if (!NameUtils.IsValidGraphQLName(responseName))
if (!responseName.IsValidGraphQLName())
{
throw new ArgumentException(
string.Format(SelectionSetOptimizerContext_InvalidFieldName, responseName));
Expand Down
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using HotChocolate.Utilities;

#nullable enable
Expand Down Expand Up @@ -106,7 +107,7 @@ public IReadOnlyList<TypeDependency> GetDependencies()
{
if (_contextData is null)
{
return ExtensionData.Empty;
return ImmutableDictionary<string, object?>.Empty;
}

return _contextData;
Expand Down
Expand Up @@ -75,6 +75,11 @@ internal sealed class NodeFieldTypeInterceptor : TypeInterceptor
}
};

// In the projection interceptor we want to change the context data that is on this field
// after the field is completed. We need at least 1 element on the context data to avoid
// it to be replaced with ExtensionData.Empty
field.ContextData[WellKnownContextData.IsNodeField] = true;

fields.Insert(index, field);
}

Expand Down Expand Up @@ -107,6 +112,11 @@ internal sealed class NodeFieldTypeInterceptor : TypeInterceptor
}
};

// In the projection interceptor we want to change the context data that is on this field
// after the field is completed. We need at least 1 element on the context data to avoid
// it to be replaced with ExtensionData.Empty
field.ContextData[WellKnownContextData.IsNodesField] = true;

fields.Insert(index, field);
}
}
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using HotChocolate.Configuration;
using HotChocolate.Properties;
Expand All @@ -13,7 +14,7 @@ public abstract class TypeSystemObjectBase<TDefinition> : TypeSystemObjectBase
where TDefinition : DefinitionBase
{
private TDefinition? _definition;
private ExtensionData? _contextData;
private IReadOnlyDictionary<string, object?>? _contextData;

public override IReadOnlyDictionary<string, object?> ContextData
{
Expand Down Expand Up @@ -129,10 +130,10 @@ internal sealed override void CompleteType(ITypeCompletionContext context)
_contextData = definition.ContextData;
_definition = null;

OnAfterCompleteType(context, definition, _contextData);
OnAfterCompleteType(context, definition, definition.ContextData);
ExecuteConfigurations(context, definition, ApplyConfigurationOn.AfterCompletion);

OnValidateType(context, definition, _contextData);
OnValidateType(context, definition, definition.ContextData);

MarkCompleted();
}
Expand All @@ -143,7 +144,7 @@ internal sealed override void FinalizeType(ITypeCompletionContext context)
// collected by the GC.
if (_contextData!.Count == 0)
{
_contextData = ExtensionData.Empty;
_contextData = ImmutableDictionary<string, object?>.Empty;
}

MarkFinalized();
Expand Down
10 changes: 8 additions & 2 deletions src/HotChocolate/Data/src/Data/DataResources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 10 additions & 5 deletions src/HotChocolate/Data/src/Data/DataResources.resx
Expand Up @@ -2,9 +2,9 @@

<root>
<xsd:schema id="root"
xmlns=""
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
xmlns=""
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">

</xsd:element>
Expand All @@ -16,11 +16,13 @@
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089
</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089
</value>
</resheader>
<data name="FilterField_FilterField_TypeUnknown" xml:space="preserve">
Expand Down Expand Up @@ -221,6 +223,9 @@
<data name="ProjectionConvention_CouldNotProject" xml:space="preserve">
<value>Projection Visitor is in invalid state. Projection failed!</value>
</data>
<data name="ProjectionConvention_NodeFieldWasInInvalidState" xml:space="preserve">
<value>The Query.node field is in a invalid state. The context data was empty, but it is expected to contain at least one element</value>
</data>
<data name="ProjectionVisitor_NodeFieldWasNotFound" xml:space="preserve">
<value>Type {0} does not contain a valid node field. Only `items` and `nodes` are supported</value>
</data>
Expand Down
Expand Up @@ -5,6 +5,7 @@
using HotChocolate.Data.Projections;
using HotChocolate.Data.Sorting;
using HotChocolate.Execution.Configuration;
using HotChocolate.Execution.Processing;
using HotChocolate.Internal;

namespace Microsoft.Extensions.DependencyInjection;
Expand Down Expand Up @@ -199,11 +200,11 @@ public static class HotChocolateDataRequestBuilderExtensions
public static IRequestExecutorBuilder AddProjections(
this IRequestExecutorBuilder builder,
Action<IProjectionConventionDescriptor> configure,
string? name = null) =>
builder.ConfigureSchema(s => s
string? name = null)
=> builder.ConfigureSchema(s => s
.TryAddTypeInterceptor<ProjectionTypeInterceptor>()
.TryAddConvention<IProjectionConvention>(
sp => new ProjectionConvention(configure),
_ => new ProjectionConvention(configure),
name));

/// <summary>
Expand All @@ -224,8 +225,8 @@ public static class HotChocolateDataRequestBuilderExtensions
public static IRequestExecutorBuilder AddProjections<TConvention>(
this IRequestExecutorBuilder builder,
string? name = null)
where TConvention : class, IProjectionConvention =>
builder.ConfigureSchema(s => s
where TConvention : class, IProjectionConvention
=> builder.ConfigureSchema(s => s
.TryAddTypeInterceptor<ProjectionTypeInterceptor>()
.TryAddConvention<IProjectionConvention, TConvention>(name));
}
@@ -1,13 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using HotChocolate.Execution.Processing;
using HotChocolate.Language;
using HotChocolate.Resolvers;
using HotChocolate.Types;
using static HotChocolate.WellKnownContextData;

namespace HotChocolate.Data.Projections.Expressions;

Expand Down Expand Up @@ -57,52 +52,27 @@ private static ApplyProjection CreateApplicator<TEntityType>()
// if projections are already applied we can skip
var skipProjection =
context.LocalContextData.TryGetValue(SkipProjectionKey, out var skip) &&
skip is true;
context.LocalContextData.TryGetValue(SkipProjectionKey, out var skip) &&
skip is true;
// ensure sorting is only applied once
context.LocalContextData =
context.LocalContextData.SetItem(SkipProjectionKey, true);
context.LocalContextData.SetItem(SkipProjectionKey, true);
if (skipProjection)
{
return input;
}
// in case we are being called from the node/nodes field we need to enrich
// the projections context with the type that shall be resolved.
Type? selectionRuntimeType = null;
ISelection? selection = null;
if (context.LocalContextData.TryGetValue(InternalType, out var value) &&
value is ObjectType objectType &&
objectType.RuntimeType != typeof(object))
{
selectionRuntimeType = objectType.RuntimeType;
var fieldProxy = new NodeFieldProxy(context.Selection.Field, objectType);
selection = CreateProxySelection(context.Selection, fieldProxy);
}
var visitorContext =
new QueryableProjectionContext(
context,
context.ObjectType,
selectionRuntimeType ?? context.Selection.Type.UnwrapRuntimeType());
var visitor = new QueryableProjectionVisitor();
context.Selection.Type.UnwrapRuntimeType());
// if we do not have a node selection proxy than this is a standard field and we
// just traverse
if (selection is null)
{
visitor.Visit(visitorContext);
}
var visitor = new QueryableProjectionVisitor();
// but if we have a node selection proxy we will push that into the visitor to use
// it instead of the selection on the context.
else
{
visitor.Visit(visitorContext, selection);
}
visitor.Visit(visitorContext);
var projection = visitorContext.Project<TEntityType>();
Expand All @@ -114,80 +84,4 @@ private static ApplyProjection CreateApplicator<TEntityType>()
_ => input
};
};

private static Selection CreateProxySelection(ISelection selection, NodeFieldProxy field)
{
var includeConditionsSource = ((Selection)selection).IncludeConditions;
var includeConditions = new long[includeConditionsSource.Length];
includeConditionsSource.CopyTo(includeConditions);

var proxy = new Selection(selection.Id, selection.DeclaringType, field, field.Type, selection.SyntaxNode, selection.ResponseName, selection.Arguments, includeConditions, selection.IsInternal, selection.Strategy != SelectionExecutionStrategy.Serial, selection.ResolverPipeline, selection.PureResolver);
proxy.SetSelectionSetId(((Selection)selection).SelectionSetId);
proxy.Seal(selection.DeclaringSelectionSet);
return proxy;
}

private sealed class NodeFieldProxy : IObjectField
{
private readonly IObjectField _nodeField;
private readonly ObjectType _type;
private readonly Type _runtimeType;

public NodeFieldProxy(IObjectField nodeField, ObjectType type)
{
_nodeField = nodeField;
_type = type;
_runtimeType = type.RuntimeType;
}

public IObjectType DeclaringType => _nodeField.DeclaringType;

public bool IsParallelExecutable => _nodeField.IsParallelExecutable;

public bool HasStreamResult => _nodeField.HasStreamResult;

public FieldDelegate Middleware => _nodeField.Middleware;

public FieldResolverDelegate? Resolver => _nodeField.Resolver;

public PureFieldDelegate? PureResolver => _nodeField.PureResolver;

public SubscribeResolverDelegate? SubscribeResolver => _nodeField.SubscribeResolver;

public IReadOnlyList<IDirective> ExecutableDirectives => _nodeField.ExecutableDirectives;

public MemberInfo? Member => _nodeField.Member;

public MemberInfo? ResolverMember => _nodeField.ResolverMember;

public bool IsIntrospectionField => _nodeField.IsIntrospectionField;

public bool IsDeprecated => _nodeField.IsDeprecated;

public string? DeprecationReason => _nodeField.DeprecationReason;

public int Index => _nodeField.Index;

public string? Description => _nodeField.Description;

public IDirectiveCollection Directives => _nodeField.Directives;

public ISyntaxNode? SyntaxNode => _nodeField.SyntaxNode;

public IReadOnlyDictionary<string, object?> ContextData => _nodeField.ContextData;

public IOutputType Type => _type;

public IFieldCollection<IInputField> Arguments => _nodeField.Arguments;

public string Name => _nodeField.Name;

public FieldCoordinate Coordinate => _nodeField.Coordinate;

public Type RuntimeType => _runtimeType;

IComplexOutputType IOutputField.DeclaringType => _nodeField.DeclaringType;

ITypeSystemObject IField.DeclaringType => ((IField)_nodeField).DeclaringType;
}
}

0 comments on commit cb9265f

Please sign in to comment.