Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed node resolver with entities #5429

Merged
merged 4 commits into from
Sep 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions src/HotChocolate/Core/src/Abstractions/ExtensionData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

namespace HotChocolate;

public class ExtensionData
public sealed class ExtensionData
michaelstaib marked this conversation as resolved.
Show resolved Hide resolved
: 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();
michaelstaib marked this conversation as resolved.
Show resolved Hide resolved
}
10 changes: 10 additions & 0 deletions src/HotChocolate/Core/src/Abstractions/WellKnownContextData.cs
Original file line number Diff line number Diff line change
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";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just came across this, while fixing a merge conflict in this file: Is it intentional that the value of both these context keys is the same or should this one be the plural variant? (Haven't reviewed the actual PR)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch! thanks tobias!

}
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ private static void CreateNodeField(
}
};

// 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 @@ private static void CreateNodesField(
}
};

// 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);
}
}
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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 IRequestExecutorBuilder AddProjections(
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 IRequestExecutorBuilder AddProjections(
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));
}
Original file line number Diff line number Diff line change
@@ -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 @@ value is ObjectType objectType &&
_ => 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;
}
}
Loading