diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 711794a8c4f..a76f831520f 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -23,7 +23,6 @@ "args": [ "build", "src/All.sln", - "-c Debug", // Ask dotnet build to generate full paths for file names. "/property:GenerateFullPaths=true", // Do not generate summary otherwise it leads to duplicate errors in Problems panel diff --git a/global.json b/global.json index 5c393dc1ff6..56187be55a3 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,5 @@ { "sdk": { - "version": "7.0.100-preview.7.22377.5", - "rollForward": "latestMinor" + "version": "7.0.100-preview.7.22377.5" } -} \ No newline at end of file +} diff --git a/src/HotChocolate/Core/src/Abstractions/WellKnownContextData.cs b/src/HotChocolate/Core/src/Abstractions/WellKnownContextData.cs index 02af101380d..b5819e47746 100644 --- a/src/HotChocolate/Core/src/Abstractions/WellKnownContextData.cs +++ b/src/HotChocolate/Core/src/Abstractions/WellKnownContextData.cs @@ -183,6 +183,11 @@ public static class WellKnownContextData /// public const string InternalId = "HotChocolate.Relay.Node.Id.InternalId"; + /// + /// The key to get the id type name from the context data. + /// + public const string InternalTypeName = "HotChocolate.Relay.Node.Id.InternalTypeName"; + /// /// The key to get the id type from the context data. /// diff --git a/src/HotChocolate/Core/src/Execution/HotChocolate.Execution.csproj b/src/HotChocolate/Core/src/Execution/HotChocolate.Execution.csproj index 9c9efc2c24f..011949fc289 100644 --- a/src/HotChocolate/Core/src/Execution/HotChocolate.Execution.csproj +++ b/src/HotChocolate/Core/src/Execution/HotChocolate.Execution.csproj @@ -14,6 +14,7 @@ + diff --git a/src/HotChocolate/Core/src/Execution/Processing/OperationCompiler.cs b/src/HotChocolate/Core/src/Execution/Processing/OperationCompiler.cs index f52ffe79189..af4419e70f1 100644 --- a/src/HotChocolate/Core/src/Execution/Processing/OperationCompiler.cs +++ b/src/HotChocolate/Core/src/Execution/Processing/OperationCompiler.cs @@ -466,7 +466,7 @@ private void CollectFields(CompilerContext context) responseName: responseName, isParallelExecutable: field.IsParallelExecutable, arguments: CoerceArgumentValues(field, selection, responseName), - includeCondition: includeCondition); + includeConditions: includeCondition == 0 ? null : new[] { includeCondition }); context.Fields.Add(responseName, preparedSelection); diff --git a/src/HotChocolate/Core/src/Execution/Processing/Selection.cs b/src/HotChocolate/Core/src/Execution/Processing/Selection.cs index ae9b373441b..300c8bd57de 100644 --- a/src/HotChocolate/Core/src/Execution/Processing/Selection.cs +++ b/src/HotChocolate/Core/src/Execution/Processing/Selection.cs @@ -29,7 +29,7 @@ public class Selection : ISelection FieldNode syntaxNode, string responseName, IArgumentMap? arguments = null, - long includeCondition = 0, + long[]? includeConditions = null, bool isInternal = false, bool isParallelExecutable = true, FieldDelegate? resolverPipeline = null, @@ -46,9 +46,7 @@ public class Selection : ISelection PureResolver = pureResolver; Strategy = InferStrategy(!isParallelExecutable, pureResolver is not null); - _includeConditions = includeCondition is 0 - ? Array.Empty() - : new[] { includeCondition }; + _includeConditions = includeConditions ?? Array.Empty(); _flags = isInternal ? Flags.Internal : Flags.None; @@ -152,6 +150,8 @@ public bool HasStreamDirective(long includeFlags) public bool IsConditional => _includeConditions.Length > 0 || (_flags & Flags.Internal) == Flags.Internal; + internal ReadOnlySpan IncludeConditions => _includeConditions; + public bool IsIncluded(long includeFlags, bool allowInternals = false) { // in most case we do not have any include condition, diff --git a/src/HotChocolate/Core/src/Fetching/Extensions/ObjectFieldDataLoaderExtensions.cs b/src/HotChocolate/Core/src/Fetching/Extensions/ObjectFieldDataLoaderExtensions.cs index 0247e4b4a9f..038c59a28ec 100644 --- a/src/HotChocolate/Core/src/Fetching/Extensions/ObjectFieldDataLoaderExtensions.cs +++ b/src/HotChocolate/Core/src/Fetching/Extensions/ObjectFieldDataLoaderExtensions.cs @@ -67,7 +67,7 @@ public static class DataLoaderObjectFieldExtensions dataLoaderType); }, definition, - ApplyConfigurationOn.Completion)); + ApplyConfigurationOn.BeforeCompletion)); }); return descriptor; diff --git a/src/HotChocolate/Core/src/Types.CursorPagination/ConnectionType.cs b/src/HotChocolate/Core/src/Types.CursorPagination/ConnectionType.cs index 1e99cee9c40..d6c150c43d5 100644 --- a/src/HotChocolate/Core/src/Types.CursorPagination/ConnectionType.cs +++ b/src/HotChocolate/Core/src/Types.CursorPagination/ConnectionType.cs @@ -58,14 +58,14 @@ internal class ConnectionType TypeContext.Output); }, Definition, - ApplyConfigurationOn.Naming, + ApplyConfigurationOn.BeforeNaming, nodeType, TypeDependencyKind.Named)); Definition.Configurations.Add( new CompleteConfiguration( (c, _) => EdgeType = c.GetType(TypeReference.Create(edgeTypeName)), Definition, - ApplyConfigurationOn.Completion)); + ApplyConfigurationOn.BeforeCompletion)); } internal ConnectionType(ITypeReference nodeType, bool withTotalCount) @@ -110,7 +110,7 @@ internal ConnectionType(ITypeReference nodeType, bool withTotalCount) TypeContext.Output); }, Definition, - ApplyConfigurationOn.Naming, + ApplyConfigurationOn.BeforeNaming, nodeType, TypeDependencyKind.Named)); Definition.Configurations.Add( @@ -120,7 +120,7 @@ internal ConnectionType(ITypeReference nodeType, bool withTotalCount) EdgeType = c.GetType(edgeType); }, Definition, - ApplyConfigurationOn.Completion)); + ApplyConfigurationOn.BeforeCompletion)); } /// diff --git a/src/HotChocolate/Core/src/Types.CursorPagination/EdgeType.cs b/src/HotChocolate/Core/src/Types.CursorPagination/EdgeType.cs index a1b73424299..25f4f847487 100644 --- a/src/HotChocolate/Core/src/Types.CursorPagination/EdgeType.cs +++ b/src/HotChocolate/Core/src/Types.CursorPagination/EdgeType.cs @@ -32,7 +32,7 @@ internal sealed class EdgeType : ObjectType, IEdgeType new CompleteConfiguration( (c, _) => NodeType = c.GetType(nodeType), Definition, - ApplyConfigurationOn.Completion)); + ApplyConfigurationOn.BeforeCompletion)); } internal EdgeType(ITypeReference nodeType) @@ -54,14 +54,14 @@ internal EdgeType(ITypeReference nodeType) ((ObjectTypeDefinition)d).Name = NameHelper.CreateEdgeName(ConnectionName); }, Definition, - ApplyConfigurationOn.Naming, + ApplyConfigurationOn.BeforeNaming, nodeType, TypeDependencyKind.Named)); Definition.Configurations.Add( new CompleteConfiguration( (c, _) => NodeType = c.GetType(nodeType), Definition, - ApplyConfigurationOn.Completion)); + ApplyConfigurationOn.BeforeCompletion)); } /// diff --git a/src/HotChocolate/Core/src/Types.Json/FromJsonSchemaDirective.cs b/src/HotChocolate/Core/src/Types.Json/FromJsonSchemaDirective.cs index 9802b09b398..7a30661e2fd 100644 --- a/src/HotChocolate/Core/src/Types.Json/FromJsonSchemaDirective.cs +++ b/src/HotChocolate/Core/src/Types.Json/FromJsonSchemaDirective.cs @@ -42,7 +42,7 @@ internal sealed class FromJsonSchemaDirective : ISchemaDirective throw ThrowHelper.CannotInferTypeFromJsonObj(ctx.Type.Name); }, fieldDef, - ApplyConfigurationOn.Completion)); + ApplyConfigurationOn.BeforeCompletion)); } } diff --git a/src/HotChocolate/Core/src/Types.OffsetPagination/CollectionSegmentType~1.cs b/src/HotChocolate/Core/src/Types.OffsetPagination/CollectionSegmentType~1.cs index 774a6f9a479..5c8c211cab4 100644 --- a/src/HotChocolate/Core/src/Types.OffsetPagination/CollectionSegmentType~1.cs +++ b/src/HotChocolate/Core/src/Types.OffsetPagination/CollectionSegmentType~1.cs @@ -41,7 +41,7 @@ internal class CollectionSegmentType definition.Name = type.NamedType().Name + "CollectionSegment"; }, Definition, - ApplyConfigurationOn.Naming, + ApplyConfigurationOn.BeforeNaming, nodeType, TypeDependencyKind.Named)); } @@ -57,7 +57,7 @@ internal class CollectionSegmentType nodes.Type = TypeReference.Parse($"[{ItemType.Print()}]", TypeContext.Output); }, Definition, - ApplyConfigurationOn.Naming, + ApplyConfigurationOn.BeforeNaming, nodeType, TypeDependencyKind.Named)); diff --git a/src/HotChocolate/Core/src/Types/SchemaErrorBuilder.cs b/src/HotChocolate/Core/src/Types/SchemaErrorBuilder.cs index d84f2d2a4f0..1a95a64166f 100644 --- a/src/HotChocolate/Core/src/Types/SchemaErrorBuilder.cs +++ b/src/HotChocolate/Core/src/Types/SchemaErrorBuilder.cs @@ -6,10 +6,9 @@ namespace HotChocolate; -public partial class SchemaErrorBuilder - : ISchemaErrorBuilder +public partial class SchemaErrorBuilder : ISchemaErrorBuilder { - private Error _error = new Error(); + private readonly Error _error = new(); public ISchemaErrorBuilder SetMessage(string message) { @@ -84,5 +83,5 @@ public ISchemaError Build() return _error.Clone(); } - public static SchemaErrorBuilder New() => new SchemaErrorBuilder(); + public static SchemaErrorBuilder New() => new(); } diff --git a/src/HotChocolate/Core/src/Types/Types/Descriptors/Contracts/ConfigurationKind.cs b/src/HotChocolate/Core/src/Types/Types/Descriptors/Contracts/ConfigurationKind.cs index 2d4bb20a401..e29b32501fe 100644 --- a/src/HotChocolate/Core/src/Types/Types/Descriptors/Contracts/ConfigurationKind.cs +++ b/src/HotChocolate/Core/src/Types/Types/Descriptors/Contracts/ConfigurationKind.cs @@ -1,8 +1,33 @@ namespace HotChocolate.Types; +/// +/// This enum defines the events on which configurations can be applied. +/// public enum ApplyConfigurationOn { + + /// + /// Before the type is created. + /// Create, - Naming, - Completion + + /// + /// Before the types name is completed. + /// + BeforeNaming, + + /// + /// After the types name is completed. + /// + AfterNaming, + + /// + /// Before the type is completed. + /// + BeforeCompletion, + + /// + /// After the type is completed. + /// + AfterCompletion } diff --git a/src/HotChocolate/Core/src/Types/Types/Descriptors/DescriptorBase~1.cs b/src/HotChocolate/Core/src/Types/Types/Descriptors/DescriptorBase~1.cs index eb61edc3a9b..1e0774cb60c 100644 --- a/src/HotChocolate/Core/src/Types/Types/Descriptors/DescriptorBase~1.cs +++ b/src/HotChocolate/Core/src/Types/Types/Descriptors/DescriptorBase~1.cs @@ -109,7 +109,7 @@ private void OnBeforeCreate(Action configure) var configuration = new CompleteConfiguration( (c, d) => configure(c, (T)d), Definition, - ApplyConfigurationOn.Naming); + ApplyConfigurationOn.BeforeNaming); Definition.Configurations.Add(configuration); @@ -130,7 +130,7 @@ private void OnBeforeCreate(Action configure) var configuration = new CompleteConfiguration( (c, d) => configure(c, (T)d), Definition, - ApplyConfigurationOn.Completion); + ApplyConfigurationOn.BeforeCompletion); Definition.Configurations.Add(configuration); diff --git a/src/HotChocolate/Core/src/Types/Types/Extensions/DirectiveCollectionExtensions.cs b/src/HotChocolate/Core/src/Types/Types/Extensions/DirectiveCollectionExtensions.cs index c410201677c..bbbd28b8c41 100644 --- a/src/HotChocolate/Core/src/Types/Types/Extensions/DirectiveCollectionExtensions.cs +++ b/src/HotChocolate/Core/src/Types/Types/Extensions/DirectiveCollectionExtensions.cs @@ -24,7 +24,7 @@ public static T SingleOrDefault(this IDirectiveCollection directives) } } - return default; + return default!; } internal static IValueNode? SkipValue(this IReadOnlyList directives) diff --git a/src/HotChocolate/Core/src/Types/Types/Pagination/PagingHelper.cs b/src/HotChocolate/Core/src/Types/Types/Pagination/PagingHelper.cs index ca03c374c32..53150486d56 100644 --- a/src/HotChocolate/Core/src/Types/Types/Pagination/PagingHelper.cs +++ b/src/HotChocolate/Core/src/Types/Types/Pagination/PagingHelper.cs @@ -43,7 +43,7 @@ public static class PagingHelper options, placeholder), definition, - ApplyConfigurationOn.Completion)); + ApplyConfigurationOn.BeforeCompletion)); return descriptor; } diff --git a/src/HotChocolate/Core/src/Types/Types/Relay/Descriptors/NodeDescriptor~1.cs b/src/HotChocolate/Core/src/Types/Types/Relay/Descriptors/NodeDescriptor~1.cs index 57b6e9e6e53..5585fcb0fd1 100644 --- a/src/HotChocolate/Core/src/Types/Types/Relay/Descriptors/NodeDescriptor~1.cs +++ b/src/HotChocolate/Core/src/Types/Types/Relay/Descriptors/NodeDescriptor~1.cs @@ -33,10 +33,16 @@ public NodeDescriptor(IObjectTypeDescriptor descriptor) { _typeDescriptor = descriptor; - _typeDescriptor - .Implements() - .Extend() - .OnBeforeCompletion(OnCompleteDefinition); + // we use the CompleteConfiguration instead of the higher level api since + // we want to target a specific event. + var ownerDef = _typeDescriptor.Implements().Extend().Definition; + + var configuration = new CompleteConfiguration( + (c, d) => OnCompleteDefinition(c, (ObjectTypeDefinition)d), + ownerDef, + ApplyConfigurationOn.AfterNaming); + + ownerDef.Configurations.Add(configuration); } private void OnCompleteDefinition( @@ -45,7 +51,21 @@ public NodeDescriptor(IObjectTypeDescriptor descriptor) { if (Definition.ResolverField is null) { - ResolveNodeWith(); + var resolverMethod = + Context.TypeInspector.GetNodeResolverMethod(typeof(TNode), typeof(TNode)); + + // we allow a node to not have a node resolver. + // this opens up type interceptors bringing these in later. + // we also introduced a validation option that makes sure that node resolvers are + // available after the schema is completed. + if (resolverMethod is not null) + { + ResolveNodeWith(resolverMethod); + } + else + { + ConfigureNodeField(); + } } CompleteResolver(context, definition); @@ -150,11 +170,13 @@ public INodeDescriptor IdField(MemberInfo propertyOrMethod) } /// - public IObjectFieldDescriptor ResolveNodeWith() => - ResolveNodeWith( + public IObjectFieldDescriptor ResolveNodeWith() + { + return ResolveNodeWith( Context.TypeInspector.GetNodeResolverMethod( typeof(TNode), typeof(TResolver))!); + } /// public IObjectFieldDescriptor ResolveNodeWith(Type type) => diff --git a/src/HotChocolate/Core/src/Types/Types/Relay/Extensions/RelayIdFieldHelpers.cs b/src/HotChocolate/Core/src/Types/Types/Relay/Extensions/RelayIdFieldHelpers.cs index 28ce042ab58..96319aa1f81 100644 --- a/src/HotChocolate/Core/src/Types/Types/Relay/Extensions/RelayIdFieldHelpers.cs +++ b/src/HotChocolate/Core/src/Types/Types/Relay/Extensions/RelayIdFieldHelpers.cs @@ -105,7 +105,7 @@ internal static class RelayIdFieldHelpers placeholder, typeName), definition, - ApplyConfigurationOn.Completion); + ApplyConfigurationOn.BeforeCompletion); definition.Configurations.Add(configuration); } diff --git a/src/HotChocolate/Core/src/Types/Types/Relay/NodeFieldResolvers.cs b/src/HotChocolate/Core/src/Types/Types/Relay/NodeFieldResolvers.cs index ded5e092658..684a0de14b3 100644 --- a/src/HotChocolate/Core/src/Types/Types/Relay/NodeFieldResolvers.cs +++ b/src/HotChocolate/Core/src/Types/Types/Relay/NodeFieldResolvers.cs @@ -2,6 +2,7 @@ using System; using System.Buffers; +using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Threading.Tasks; using HotChocolate.Execution; @@ -36,7 +37,7 @@ internal static class NodeFieldResolvers type.ContextData.TryGetValue(NodeResolver, out var o) && o is NodeResolverInfo nodeResolverInfo) { - SetLocalContext(context, nodeId, deserializedId, typeName); + SetLocalContext(context, nodeId, deserializedId, type); TryReplaceArguments(context, nodeResolverInfo, Id, nodeId); await nodeResolverInfo.Pipeline.Invoke(context); @@ -86,7 +87,7 @@ internal static class NodeFieldResolvers { var nodeContext = context.Clone(); - SetLocalContext(nodeContext, nodeId, deserializedId, typeName); + SetLocalContext(nodeContext, nodeId, deserializedId, type); TryReplaceArguments(nodeContext, nodeResolverInfo, Ids, nodeId); tasks[i] = ExecutePipelineAsync(nodeContext, nodeResolverInfo); @@ -155,7 +156,7 @@ internal static class NodeFieldResolvers { var nodeContext = context.Clone(); - SetLocalContext(nodeContext, nodeId, deserializedId, typeName); + SetLocalContext(nodeContext, nodeId, deserializedId, type); TryReplaceArguments(nodeContext, nodeResolverInfo, Ids, nodeId); result[0] = await ExecutePipelineAsync(nodeContext, nodeResolverInfo); @@ -188,11 +189,12 @@ internal static class NodeFieldResolvers IMiddlewareContext context, StringValueNode nodeId, IdValue deserializedId, - string typeName) + ObjectType type) { context.SetLocalState(NodeId, nodeId.Value); context.SetLocalState(InternalId, deserializedId.Value); - context.SetLocalState(InternalType, typeName); + context.SetLocalState(InternalType, type); + context.SetLocalState(InternalTypeName, type.Name); context.SetLocalState(WellKnownContextData.IdValue, deserializedId); } diff --git a/src/HotChocolate/Core/src/Types/Types/Relay/NodeFieldTypeInterceptor.cs b/src/HotChocolate/Core/src/Types/Types/Relay/NodeFieldTypeInterceptor.cs index 6ff878f4f35..15d541268e9 100644 --- a/src/HotChocolate/Core/src/Types/Types/Relay/NodeFieldTypeInterceptor.cs +++ b/src/HotChocolate/Core/src/Types/Types/Relay/NodeFieldTypeInterceptor.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using HotChocolate.Configuration; -using HotChocolate.Language; using HotChocolate.Types.Descriptors; using HotChocolate.Types.Descriptors.Definitions; using HotChocolate.Types.Introspection; diff --git a/src/HotChocolate/Core/src/Types/Types/Relay/NodeResolverInfo.cs b/src/HotChocolate/Core/src/Types/Types/Relay/NodeResolverInfo.cs index a1a092f82ba..beedb08878c 100644 --- a/src/HotChocolate/Core/src/Types/Types/Relay/NodeResolverInfo.cs +++ b/src/HotChocolate/Core/src/Types/Types/Relay/NodeResolverInfo.cs @@ -1,19 +1,22 @@ #nullable enable +using System.Diagnostics.CodeAnalysis; using HotChocolate.Resolvers; namespace HotChocolate.Types.Relay; /// -/// The node resolver info is used by the fields node and nodes to execute +/// The node resolver info is used by the fields node and nodes to execute /// the node resolver pipeline for a specific node. /// internal sealed class NodeResolverInfo { - public NodeResolverInfo(Argument? id, FieldDelegate pipeline) + public NodeResolverInfo(ObjectField? resolverField, FieldDelegate pipeline) { - Id = id; + Id = resolverField?.Arguments[0]; Pipeline = pipeline; + QueryField = resolverField; + IsQueryFieldResolver = resolverField is not null; } /// @@ -26,4 +29,18 @@ public NodeResolverInfo(Argument? id, FieldDelegate pipeline) /// Gets the node resolver pipeline. /// public FieldDelegate Pipeline { get; } + + /// + /// Gets the query field from which we inferred the node resolver. + /// + public ObjectField? QueryField { get; } + + /// + /// Defines if the node resolver was inferred from a query field. + /// + /// +#if NET5_0_OR_GREATER + [MemberNotNullWhen(true, nameof(QueryField), nameof(Id))] +#endif + public bool IsQueryFieldResolver { get; } } diff --git a/src/HotChocolate/Core/src/Types/Types/Relay/NodeResolverTypeInterceptor.cs b/src/HotChocolate/Core/src/Types/Types/Relay/NodeResolverTypeInterceptor.cs index 860ca3b8e6c..2eedf10d5bd 100644 --- a/src/HotChocolate/Core/src/Types/Types/Relay/NodeResolverTypeInterceptor.cs +++ b/src/HotChocolate/Core/src/Types/Types/Relay/NodeResolverTypeInterceptor.cs @@ -191,7 +191,8 @@ public override void OnAfterCompleteTypes() { var fieldName = (string)node[NodeResolver]!; var field = QueryType.Fields[fieldName]; - node[NodeResolver] = new NodeResolverInfo(field.Arguments[0], field.Middleware); + + node[NodeResolver] = new NodeResolverInfo(field, field.Middleware); } } } diff --git a/src/HotChocolate/Core/src/Types/Types/TypeSystemObjectBase~1.cs b/src/HotChocolate/Core/src/Types/Types/TypeSystemObjectBase~1.cs index 19408246d8f..bf49d0ee093 100644 --- a/src/HotChocolate/Core/src/Types/Types/TypeSystemObjectBase~1.cs +++ b/src/HotChocolate/Core/src/Types/Types/TypeSystemObjectBase~1.cs @@ -80,7 +80,7 @@ internal sealed override void CompleteName(ITypeCompletionContext context) OnBeforeCompleteName(context, definition, definition.ContextData); - ExecuteConfigurations(context, definition, ApplyConfigurationOn.Naming); + ExecuteConfigurations(context, definition, ApplyConfigurationOn.BeforeNaming); OnCompleteName(context, definition); Debug.Assert( @@ -99,6 +99,7 @@ internal sealed override void CompleteName(ITypeCompletionContext context) } OnAfterCompleteName(context, definition, definition.ContextData); + ExecuteConfigurations(context, definition, ApplyConfigurationOn.AfterNaming); MarkNamed(); } @@ -121,7 +122,7 @@ internal sealed override void CompleteType(ITypeCompletionContext context) OnBeforeCompleteType(context, definition, definition.ContextData); - ExecuteConfigurations(context, definition, ApplyConfigurationOn.Completion); + ExecuteConfigurations(context, definition, ApplyConfigurationOn.BeforeCompletion); Description = definition.Description; OnCompleteType(context, definition); @@ -129,6 +130,8 @@ internal sealed override void CompleteType(ITypeCompletionContext context) _definition = null; OnAfterCompleteType(context, definition, _contextData); + ExecuteConfigurations(context, definition, ApplyConfigurationOn.AfterCompletion); + OnValidateType(context, definition, _contextData); MarkCompleted(); diff --git a/src/HotChocolate/Core/test/Types.Tests/Configuration/TypeInitializationOrderTests.cs b/src/HotChocolate/Core/test/Types.Tests/Configuration/TypeInitializationOrderTests.cs index fa9732d6d11..0d247685cec 100644 --- a/src/HotChocolate/Core/test/Types.Tests/Configuration/TypeInitializationOrderTests.cs +++ b/src/HotChocolate/Core/test/Types.Tests/Configuration/TypeInitializationOrderTests.cs @@ -61,7 +61,7 @@ protected override void Configure(IObjectTypeDescriptor descriptor) } }, d, - ApplyConfigurationOn.Completion, + ApplyConfigurationOn.BeforeCompletion, reference, TypeDependencyKind.Completed)); }); diff --git a/src/HotChocolate/Data/src/Data/Filters/Extensions/FilterObjectFieldDescriptorExtensions.cs b/src/HotChocolate/Data/src/Data/Filters/Extensions/FilterObjectFieldDescriptorExtensions.cs index c5f14971825..6e42b7e9415 100644 --- a/src/HotChocolate/Data/src/Data/Filters/Extensions/FilterObjectFieldDescriptorExtensions.cs +++ b/src/HotChocolate/Data/src/Data/Filters/Extensions/FilterObjectFieldDescriptorExtensions.cs @@ -187,7 +187,7 @@ public static class FilterObjectFieldDescriptorExtensions placeholder, scope), definition, - ApplyConfigurationOn.Completion, + ApplyConfigurationOn.BeforeCompletion, argumentTypeReference, TypeDependencyKind.Completed)); @@ -197,7 +197,7 @@ public static class FilterObjectFieldDescriptorExtensions argDef.Name = context.GetFilterConvention(scope).GetArgumentName(), argumentDefinition, - ApplyConfigurationOn.Naming)); + ApplyConfigurationOn.BeforeNaming)); }); return descriptor; diff --git a/src/HotChocolate/Data/src/Data/Filters/FilterInputType.cs b/src/HotChocolate/Data/src/Data/Filters/FilterInputType.cs index 4ba17a0b026..ad274a49ceb 100644 --- a/src/HotChocolate/Data/src/Data/Filters/FilterInputType.cs +++ b/src/HotChocolate/Data/src/Data/Filters/FilterInputType.cs @@ -188,7 +188,7 @@ protected virtual void Configure(IFilterInputTypeDescriptor descriptor) new CompleteConfiguration( CreateNamingConfiguration, filterTypeDefinition, - ApplyConfigurationOn.Naming, + ApplyConfigurationOn.BeforeNaming, new TypeDependency[] { new(filterOperationType, TypeDependencyKind.Named) , new(filterType, TypeDependencyKind.Named) @@ -197,7 +197,7 @@ protected virtual void Configure(IFilterInputTypeDescriptor descriptor) new CompleteConfiguration( CreateOperationFieldConfiguration, filterTypeDefinition, - ApplyConfigurationOn.Naming, + ApplyConfigurationOn.BeforeNaming, new TypeDependency[] { new(filterOperationType, TypeDependencyKind.Named), new(filterType, TypeDependencyKind.Named) diff --git a/src/HotChocolate/Data/src/Data/Filters/Visitor/IFilterVisitorContext.cs b/src/HotChocolate/Data/src/Data/Filters/Visitor/IFilterVisitorContext.cs index 57b3f7f5a13..6ecf2ad5dbe 100644 --- a/src/HotChocolate/Data/src/Data/Filters/Visitor/IFilterVisitorContext.cs +++ b/src/HotChocolate/Data/src/Data/Filters/Visitor/IFilterVisitorContext.cs @@ -8,8 +8,7 @@ namespace HotChocolate.Data.Filters; /// /// A context object that is passed along the visitation cycle /// -public interface IFilterVisitorContext - : ISyntaxVisitorContext +public interface IFilterVisitorContext : ISyntaxVisitorContext { /// /// The already visited types diff --git a/src/HotChocolate/Data/src/Data/Projections/Expressions/Handlers/QueryableProjectionFieldHandler.cs b/src/HotChocolate/Data/src/Data/Projections/Expressions/Handlers/QueryableProjectionFieldHandler.cs index 3149867832d..de680208abf 100644 --- a/src/HotChocolate/Data/src/Data/Projections/Expressions/Handlers/QueryableProjectionFieldHandler.cs +++ b/src/HotChocolate/Data/src/Data/Projections/Expressions/Handlers/QueryableProjectionFieldHandler.cs @@ -3,7 +3,6 @@ using System.Linq.Expressions; using System.Reflection; using HotChocolate.Execution.Processing; -using HotChocolate.Types; using static HotChocolate.Data.Projections.Expressions.ProjectionExpressionBuilder; namespace HotChocolate.Data.Projections.Expressions.Handlers; @@ -59,7 +58,7 @@ public class QueryableProjectionFieldHandler return false; } - // Deque last + // Dequeue last var scope = context.PopScope(); if (scope is not QueryableProjectionScope queryableScope) diff --git a/src/HotChocolate/Data/src/Data/Projections/Expressions/Interceptor/QueryableFilterInterceptor.cs b/src/HotChocolate/Data/src/Data/Projections/Expressions/Interceptor/QueryableFilterInterceptor.cs index b10fde0ec6a..86279cd8ca2 100644 --- a/src/HotChocolate/Data/src/Data/Projections/Expressions/Interceptor/QueryableFilterInterceptor.cs +++ b/src/HotChocolate/Data/src/Data/Projections/Expressions/Interceptor/QueryableFilterInterceptor.cs @@ -36,7 +36,7 @@ public class QueryableFilterInterceptor : IProjectionFieldInterceptor 0 && context.Selection.Peek().Arguments - .TryCoerceArguments(context.Context, out var coercedArgs) && + .TryCoerceArguments(context.ResolverContext, out var coercedArgs) && coercedArgs.TryGetValue(argumentName, out var argumentValue) && argumentValue.Type is IFilterInputType filterInputType && argumentValue.ValueLiteral is { } valueNode and not NullValueNode) diff --git a/src/HotChocolate/Data/src/Data/Projections/Expressions/Interceptor/QueryableSortInterceptor.cs b/src/HotChocolate/Data/src/Data/Projections/Expressions/Interceptor/QueryableSortInterceptor.cs index 4a3dd368f2d..5fbd71ba9cf 100644 --- a/src/HotChocolate/Data/src/Data/Projections/Expressions/Interceptor/QueryableSortInterceptor.cs +++ b/src/HotChocolate/Data/src/Data/Projections/Expressions/Interceptor/QueryableSortInterceptor.cs @@ -36,7 +36,7 @@ public class QueryableSortInterceptor : IProjectionFieldInterceptor 0 && context.Selection.Peek().Arguments - .TryCoerceArguments(context.Context, out var coercedArgs) && + .TryCoerceArguments(context.ResolverContext, out var coercedArgs) && coercedArgs.TryGetValue(argumentName, out var argumentValue) && argumentValue.Type is ListType lt && lt.ElementType is NonNullType nn && diff --git a/src/HotChocolate/Data/src/Data/Projections/Expressions/QueryableProjectionProvider.cs b/src/HotChocolate/Data/src/Data/Projections/Expressions/QueryableProjectionProvider.cs index 7315b891514..afcbae5dd34 100644 --- a/src/HotChocolate/Data/src/Data/Projections/Expressions/QueryableProjectionProvider.cs +++ b/src/HotChocolate/Data/src/Data/Projections/Expressions/QueryableProjectionProvider.cs @@ -1,10 +1,13 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Linq.Expressions; +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; @@ -26,7 +29,7 @@ public QueryableProjectionProvider(Action configu public override FieldMiddleware CreateExecutor() { - var applyProjection = CreateApplicatorAsync(); + var applyProjection = CreateApplicator(); return next => context => ExecuteAsync(next, context); @@ -44,9 +47,8 @@ public override FieldMiddleware CreateExecutor() } } - private static ApplyProjection CreateApplicatorAsync() - { - return (context, input) => + private static ApplyProjection CreateApplicator() + => (context, input) => { if (input is null) { @@ -67,27 +69,125 @@ private static ApplyProjection CreateApplicatorAsync() 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, - context.Selection.Type.UnwrapRuntimeType()); + selectionRuntimeType ?? context.Selection.Type.UnwrapRuntimeType()); var visitor = new QueryableProjectionVisitor(); - visitor.Visit(visitorContext); - var projection = - visitorContext.Project(); + // 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); + } + + // 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); + } + + var projection = visitorContext.Project(); - input = input switch + return input switch { IQueryable q => q.Select(projection), IEnumerable e => e.AsQueryable().Select(projection), - QueryableExecutable ex => - ex.WithSource(ex.Source.Select(projection)), + QueryableExecutable ex => ex.WithSource(ex.Source.Select(projection)), _ => input }; - - return 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 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 ContextData => _nodeField.ContextData; + + public IOutputType Type => _type; + + public IFieldCollection 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; } } diff --git a/src/HotChocolate/Data/src/Data/Projections/Expressions/QueryableProjectionVisitor.cs b/src/HotChocolate/Data/src/Data/Projections/Expressions/QueryableProjectionVisitor.cs index ca0e6b93646..7f5d740c2a4 100644 --- a/src/HotChocolate/Data/src/Data/Projections/Expressions/QueryableProjectionVisitor.cs +++ b/src/HotChocolate/Data/src/Data/Projections/Expressions/QueryableProjectionVisitor.cs @@ -17,7 +17,7 @@ public class QueryableProjectionVisitor : ProjectionVisitor field.Type.Kind != TypeKind.NonNull && field.Type.InnerType() is IPageType || + => (field.Type.Kind != TypeKind.NonNull && field.Type.InnerType() is IPageType) || field.Type is IPageType || field.ContextData.ContainsKey(ProjectionContextIdentifier); } diff --git a/src/HotChocolate/Data/src/Data/Projections/Extensions/SelectionObjectFieldDescriptorExtensions.cs b/src/HotChocolate/Data/src/Data/Projections/Extensions/SelectionObjectFieldDescriptorExtensions.cs index 9d358fba5e0..e81f40185ea 100644 --- a/src/HotChocolate/Data/src/Data/Projections/Extensions/SelectionObjectFieldDescriptorExtensions.cs +++ b/src/HotChocolate/Data/src/Data/Projections/Extensions/SelectionObjectFieldDescriptorExtensions.cs @@ -3,7 +3,6 @@ using HotChocolate.Configuration; using HotChocolate.Data; using HotChocolate.Data.Projections; -using HotChocolate.Internal; using HotChocolate.Resolvers; using HotChocolate.Types.Descriptors.Definitions; using static HotChocolate.Data.Projections.ProjectionProvider; @@ -18,13 +17,16 @@ public static class ProjectionObjectFieldDescriptorExtensions .GetMethod(nameof(CreateMiddleware), BindingFlags.Static | BindingFlags.NonPublic)!; /// + /// /// Configure if this field should be projected by or if it /// should be skipped - /// + /// + /// /// if is false, this field will never be projected even if /// it is in the selection set /// if is true, this field will always be projected even it /// it is not in the selection set + /// /// /// The descriptor /// @@ -101,13 +103,13 @@ public static class ProjectionObjectFieldDescriptorExtensions /// uses the registered to apply the projections /// /// The descriptor + /// + /// The of the resolved field + /// /// /// Specify which is used, based on the value passed in /// /// - /// - /// The of the resolved field - /// /// The descriptor passed in by /// /// In case the descriptor is null @@ -159,7 +161,7 @@ public static class ProjectionObjectFieldDescriptorExtensions c, scope), definition, - ApplyConfigurationOn.Completion)); + ApplyConfigurationOn.BeforeCompletion)); }); return descriptor; @@ -172,8 +174,7 @@ public static class ProjectionObjectFieldDescriptorExtensions ITypeCompletionContext context, string? scope) { - var convention = - context.DescriptorContext.GetProjectionConvention(scope); + var convention = context.DescriptorContext.GetProjectionConvention(scope); RegisterOptimizer(definition.ContextData, convention.CreateOptimizer()); definition.ContextData[ProjectionContextIdentifier] = true; diff --git a/src/HotChocolate/Data/src/Data/Projections/Extensions/SingleOrDefaultObjectFieldDescriptorExtensions.cs b/src/HotChocolate/Data/src/Data/Projections/Extensions/SingleOrDefaultObjectFieldDescriptorExtensions.cs index bcd17dcdeae..0ae08b4952a 100644 --- a/src/HotChocolate/Data/src/Data/Projections/Extensions/SingleOrDefaultObjectFieldDescriptorExtensions.cs +++ b/src/HotChocolate/Data/src/Data/Projections/Extensions/SingleOrDefaultObjectFieldDescriptorExtensions.cs @@ -68,7 +68,7 @@ public static class SingleOrDefaultObjectFieldDescriptorExtensions middlewareDefinition); }, definition, - ApplyConfigurationOn.Completion)); + ApplyConfigurationOn.BeforeCompletion)); }); return descriptor; diff --git a/src/HotChocolate/Data/src/Data/Projections/ISelectionVisitorContext.cs b/src/HotChocolate/Data/src/Data/Projections/ISelectionVisitorContext.cs index b3c92d7ce62..e70bc11e4f7 100644 --- a/src/HotChocolate/Data/src/Data/Projections/ISelectionVisitorContext.cs +++ b/src/HotChocolate/Data/src/Data/Projections/ISelectionVisitorContext.cs @@ -1,6 +1,6 @@ +using System; using System.Collections.Generic; using HotChocolate.Execution.Processing; -using HotChocolate.Language; using HotChocolate.Resolvers; using HotChocolate.Types; @@ -12,5 +12,8 @@ public interface ISelectionVisitorContext Stack ResolvedType { get; } - IResolverContext Context { get; } + [Obsolete("Use ResolverContext")] + IResolverContext Context => ResolverContext; + + IResolverContext ResolverContext { get; } } diff --git a/src/HotChocolate/Data/src/Data/Projections/ProjectionOptimizer.cs b/src/HotChocolate/Data/src/Data/Projections/ProjectionOptimizer.cs index 8a95bcc06de..11a3aa491cd 100644 --- a/src/HotChocolate/Data/src/Data/Projections/ProjectionOptimizer.cs +++ b/src/HotChocolate/Data/src/Data/Projections/ProjectionOptimizer.cs @@ -4,7 +4,7 @@ namespace HotChocolate.Data.Projections; -public class ProjectionOptimizer : ISelectionSetOptimizer +internal sealed class ProjectionOptimizer : ISelectionSetOptimizer { private readonly IProjectionProvider _provider; diff --git a/src/HotChocolate/Data/src/Data/Projections/ProjectionVisitor.cs b/src/HotChocolate/Data/src/Data/Projections/ProjectionVisitor.cs index ff42000ec46..5577278ad83 100644 --- a/src/HotChocolate/Data/src/Data/Projections/ProjectionVisitor.cs +++ b/src/HotChocolate/Data/src/Data/Projections/ProjectionVisitor.cs @@ -11,8 +11,13 @@ public class ProjectionVisitor { public virtual void Visit(TContext context) { - context.Selection.Push((ISelection)context.Context.Selection); - Visit(context.Context.Selection.Field, context); + Visit(context, context.ResolverContext.Selection); + } + + public virtual void Visit(TContext context, ISelection selection) + { + context.Selection.Push(selection); + Visit(selection.Field, context); } protected override TContext OnBeforeLeave(ISelection selection, TContext localContext) @@ -123,7 +128,7 @@ protected override ISelectionVisitorAction Visit(IOutputField field, TContext co if (field.Type is IPageType and ObjectType pageType && context.Selection.Peek() is { } pagingFieldSelection) { - var selections = context.Context.GetSelections(pageType, pagingFieldSelection, true); + var selections = context.ResolverContext.GetSelections(pageType, pagingFieldSelection, true); foreach (var selection in selections) { diff --git a/src/HotChocolate/Data/src/Data/Projections/SelectionVisitorContext.cs b/src/HotChocolate/Data/src/Data/Projections/SelectionVisitorContext.cs index d98ed1f5072..e03ac7eabce 100644 --- a/src/HotChocolate/Data/src/Data/Projections/SelectionVisitorContext.cs +++ b/src/HotChocolate/Data/src/Data/Projections/SelectionVisitorContext.cs @@ -13,7 +13,7 @@ public SelectionVisitorContext(IResolverContext context) Selection = new Stack(); SelectionSetNodes = new Stack(); ResolvedType = new Stack(); - Context = context; + ResolverContext = context; } public Stack Selection { get; } @@ -22,5 +22,5 @@ public SelectionVisitorContext(IResolverContext context) public Stack ResolvedType { get; } - public IResolverContext Context { get; } + public IResolverContext ResolverContext { get; } } diff --git a/src/HotChocolate/Data/src/Data/Projections/SelectionVisitor`1.cs b/src/HotChocolate/Data/src/Data/Projections/SelectionVisitor`1.cs index f2c0010fd1f..00ff8f81d38 100644 --- a/src/HotChocolate/Data/src/Data/Projections/SelectionVisitor`1.cs +++ b/src/HotChocolate/Data/src/Data/Projections/SelectionVisitor`1.cs @@ -64,12 +64,10 @@ public class SelectionVisitor var result = Enter(selection, localContext); localContext = OnAfterEnter(selection, localContext, result); - if (result.Kind == SelectionVisitorActionKind.Continue) + if (result.Kind == SelectionVisitorActionKind.Continue && + VisitChildren(selection, context).Kind == SelectionVisitorActionKind.Break) { - if (VisitChildren(selection, context).Kind == SelectionVisitorActionKind.Break) - { - return Break; - } + return Break; } if (result.Kind == SelectionVisitorActionKind.Continue || @@ -113,9 +111,8 @@ protected virtual ISelectionVisitorAction VisitChildren(IOutputField field, TCon var namedType = type.NamedType(); if (namedType.IsAbstractType()) { - var possibleTypes = context.Context.Schema.GetPossibleTypes(field.Type.NamedType()); - - foreach (var possibleType in possibleTypes) + foreach (var possibleType in + context.ResolverContext.Schema.GetPossibleTypes(field.Type.NamedType())) { var result = VisitObjectType(field, possibleType, selection, context); @@ -143,11 +140,11 @@ protected virtual ISelectionVisitorAction VisitChildren(IOutputField field, TCon try { - var selections = context.Context.GetSelections(objectType, selection, true); + var selections = context.ResolverContext.GetSelections(objectType, selection, true); for (var i = 0; i < selections.Count; i++) { - var result = Visit((ISelection)selections[i], context); + var result = Visit(selections[i], context); if (result.Kind is SelectionVisitorActionKind.Break) { return Break; diff --git a/src/HotChocolate/Data/src/Data/Sorting/Extensions/SortObjectFieldDescriptorExtensions.cs b/src/HotChocolate/Data/src/Data/Sorting/Extensions/SortObjectFieldDescriptorExtensions.cs index 4217d671437..8e1fc08e84e 100644 --- a/src/HotChocolate/Data/src/Data/Sorting/Extensions/SortObjectFieldDescriptorExtensions.cs +++ b/src/HotChocolate/Data/src/Data/Sorting/Extensions/SortObjectFieldDescriptorExtensions.cs @@ -184,7 +184,7 @@ public static class SortObjectFieldDescriptorExtensions def.Type = TypeReference.Parse($"[{namedType.Name}!]"); }, argumentDefinition, - ApplyConfigurationOn.Naming, + ApplyConfigurationOn.BeforeNaming, argumentTypeReference, TypeDependencyKind.Named)); @@ -200,7 +200,7 @@ public static class SortObjectFieldDescriptorExtensions placeholder, scope), definition, - ApplyConfigurationOn.Completion, + ApplyConfigurationOn.BeforeCompletion, argumentTypeReference, TypeDependencyKind.Completed)); @@ -210,7 +210,7 @@ public static class SortObjectFieldDescriptorExtensions argDef.Name = context.GetSortConvention(scope).GetArgumentName(), argumentDefinition, - ApplyConfigurationOn.Naming)); + ApplyConfigurationOn.BeforeNaming)); }); return descriptor; diff --git a/src/HotChocolate/Data/src/Data/Sorting/SortInputType.cs b/src/HotChocolate/Data/src/Data/Sorting/SortInputType.cs index 26256a6d5eb..f1ae5e2ec35 100644 --- a/src/HotChocolate/Data/src/Data/Sorting/SortInputType.cs +++ b/src/HotChocolate/Data/src/Data/Sorting/SortInputType.cs @@ -147,7 +147,7 @@ protected sealed override void Configure(IInputObjectTypeDescriptor descriptor) new CompleteConfiguration( CreateNamingConfiguration, sortTypeDefinition, - ApplyConfigurationOn.Naming, + ApplyConfigurationOn.BeforeNaming, new TypeDependency[] { new(sortOperationType, TypeDependencyKind.Named), diff --git a/src/HotChocolate/Data/src/Data/Sorting/Visitor/ISortVisitorContext.cs b/src/HotChocolate/Data/src/Data/Sorting/Visitor/ISortVisitorContext.cs index b62e7ec0e1a..1f759a10ebf 100644 --- a/src/HotChocolate/Data/src/Data/Sorting/Visitor/ISortVisitorContext.cs +++ b/src/HotChocolate/Data/src/Data/Sorting/Visitor/ISortVisitorContext.cs @@ -4,8 +4,7 @@ namespace HotChocolate.Data.Sorting; -public interface ISortVisitorContext - : ISyntaxVisitorContext +public interface ISortVisitorContext : ISyntaxVisitorContext { Stack Types { get; } diff --git a/src/HotChocolate/Data/test/Data.Projections.Tests/IntegrationTests.cs b/src/HotChocolate/Data/test/Data.Projections.Tests/IntegrationTests.cs index 4f952405d4b..647b001cccf 100644 --- a/src/HotChocolate/Data/test/Data.Projections.Tests/IntegrationTests.cs +++ b/src/HotChocolate/Data/test/Data.Projections.Tests/IntegrationTests.cs @@ -4,6 +4,7 @@ using CookieCrumble; using HotChocolate.Execution; using HotChocolate.Types; +using HotChocolate.Types.Relay; using Microsoft.Extensions.DependencyInjection; namespace HotChocolate.Data; @@ -113,6 +114,36 @@ public async Task Projection_Should_NotBreakProjections_When_ExtensionsObjectReq result.MatchSnapshot(); } + + [Fact] + public async Task Node_Resolver_With_SingleOrDefault_Schema() + { + var schema = await new ServiceCollection() + .AddGraphQL() + .AddQueryType() + .AddObjectType(d => d.ImplementsNode().IdField(t => t.Bar)) + .AddGlobalObjectIdentification() + .AddProjections() + .BuildSchemaAsync(); + + schema.MatchSnapshot(); + } + + [Fact] + public async Task Node_Resolver_With_SingleOrDefault() + { + var executor = await new ServiceCollection() + .AddGraphQL() + .AddQueryType() + .AddObjectType(d => d.ImplementsNode().IdField(t => t.Bar)) + .AddGlobalObjectIdentification() + .AddProjections() + .BuildRequestExecutorAsync(); + + var result = await executor.ExecuteAsync(@"{ node(id: ""Rm9vCmRB"") { id __typename } }"); + + result.MatchSnapshot(); + } } public class Query @@ -120,8 +151,8 @@ public class Query [UseProjection] public IQueryable Foos => new Foo[] { - new() { Bar = "A" }, - new() { Bar = "B" } + new() { Bar = "A" }, + new() { Bar = "B" } }.AsQueryable(); } @@ -147,3 +178,22 @@ public class Foo { public string? Bar { get; set; } } + +public class QueryWithNodeResolvers +{ + [UseProjection] + public IQueryable All() + => new Foo[] + { + new() { Bar = "A" }, + }.AsQueryable(); + + [NodeResolver] + [UseSingleOrDefault] + [UseProjection] + public IQueryable GetById(string id) + => new Foo[] + { + new() { Bar = "A" }, + }.AsQueryable(); +} diff --git a/src/HotChocolate/Data/test/Data.Projections.Tests/__snapshots__/IntegrationTests.Node_Resolver_With_SingleOrDefault.snap b/src/HotChocolate/Data/test/Data.Projections.Tests/__snapshots__/IntegrationTests.Node_Resolver_With_SingleOrDefault.snap new file mode 100644 index 00000000000..5409d1bcbc9 --- /dev/null +++ b/src/HotChocolate/Data/test/Data.Projections.Tests/__snapshots__/IntegrationTests.Node_Resolver_With_SingleOrDefault.snap @@ -0,0 +1,9 @@ +{ + "data": { + "all": [ + { + "id": "Rm9vCmRB" + } + ] + } +} \ No newline at end of file diff --git a/src/HotChocolate/Data/test/Data.Projections.Tests/__snapshots__/IntegrationTests.Node_Resolver_With_SingleOrDefault_Schema.graphql b/src/HotChocolate/Data/test/Data.Projections.Tests/__snapshots__/IntegrationTests.Node_Resolver_With_SingleOrDefault_Schema.graphql new file mode 100644 index 00000000000..9fc08d4284b --- /dev/null +++ b/src/HotChocolate/Data/test/Data.Projections.Tests/__snapshots__/IntegrationTests.Node_Resolver_With_SingleOrDefault_Schema.graphql @@ -0,0 +1,27 @@ +schema { + query: QueryWithNodeResolvers +} + +"The node interface is implemented by entities that have a global unique identifier." +interface Node { + id: ID! +} + +type Foo implements Node { + id: ID! +} + +type QueryWithNodeResolvers { + "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]! + all: [Foo!]! + byId(id: ID!): Foo +} + +"The `@defer` directive may be provided for fragment spreads and inline fragments to inform the executor to delay the execution of the current fragment to indicate deprioritization of the current fragment. A query with `@defer` directive will cause the request to potentially return multiple responses, where non-deferred data is delivered in the initial response and data deferred is delivered in a subsequent response. `@include` and `@skip` take precedence over `@defer`." +directive @defer("If this argument label has a value other than null, it will be passed on to the result of this defer directive. This label is intended to give client applications a way to identify to which fragment a deferred result belongs to." label: String "Deferred when true." if: Boolean) on FRAGMENT_SPREAD | INLINE_FRAGMENT + +"The `@stream` directive may be provided for a field of `List` type so that the backend can leverage technology such as asynchronous iterators to provide a partial list in the initial response, and additional list items in subsequent responses. `@include` and `@skip` take precedence over `@stream`." +directive @stream("If this argument label has a value other than null, it will be passed on to the result of this stream directive. This label is intended to give client applications a way to identify to which fragment a streamed result belongs to." label: String "The initial elements that shall be send down to the consumer." initialCount: Int! = 0 "Streamed when true." if: Boolean) on FIELD \ No newline at end of file diff --git a/src/HotChocolate/Filters/src/Types.Filters/Extensions/FilterObjectFieldDescriptorExtensions.cs b/src/HotChocolate/Filters/src/Types.Filters/Extensions/FilterObjectFieldDescriptorExtensions.cs index 2e8d8bcc286..0d6d119b445 100644 --- a/src/HotChocolate/Filters/src/Types.Filters/Extensions/FilterObjectFieldDescriptorExtensions.cs +++ b/src/HotChocolate/Filters/src/Types.Filters/Extensions/FilterObjectFieldDescriptorExtensions.cs @@ -133,7 +133,7 @@ public static class FilterObjectFieldDescriptorExtensions argumentTypeReference, placeholder), definition, - ApplyConfigurationOn.Completion, + ApplyConfigurationOn.BeforeCompletion, argumentTypeReference, TypeDependencyKind.Completed); @@ -212,9 +212,9 @@ public static class FilterObjectFieldDescriptorExtensions d.Name = convention.ArgumentName; }, definition, - ApplyConfigurationOn.Completion); + ApplyConfigurationOn.BeforeCompletion); definition.Configurations.Add(argumentConfig); return definition; } -} \ No newline at end of file +} diff --git a/src/HotChocolate/Filters/src/Types.Sorting/Extensions/SortObjectFieldDescriptorExtensions.cs b/src/HotChocolate/Filters/src/Types.Sorting/Extensions/SortObjectFieldDescriptorExtensions.cs index 27d7c03e0cb..03dc0fa3c09 100644 --- a/src/HotChocolate/Filters/src/Types.Sorting/Extensions/SortObjectFieldDescriptorExtensions.cs +++ b/src/HotChocolate/Filters/src/Types.Sorting/Extensions/SortObjectFieldDescriptorExtensions.cs @@ -104,7 +104,8 @@ public static class SortObjectFieldDescriptorExtensions d.Name = convention.ArgumentName; }, argumentDefinition, - ApplyConfigurationOn.Completion)); + ApplyConfigurationOn. + BeforeCompletion)); definition.Arguments.Add(argumentDefinition); definition.Configurations.Add( @@ -116,7 +117,7 @@ public static class SortObjectFieldDescriptorExtensions argumentTypeReference, placeholder), definition, - ApplyConfigurationOn.Completion, + ApplyConfigurationOn.BeforeCompletion, argumentTypeReference, TypeDependencyKind.Completed)); }); @@ -187,4 +188,4 @@ public static class SortObjectFieldDescriptorExtensions definition.MiddlewareDefinitions[index] = new(middleware, key: WellKnownMiddleware.Sorting); } -} \ No newline at end of file +}