From 1f344228e3dbc61047bedc39c3373016c892176c Mon Sep 17 00:00:00 2001 From: Michael Staib Date: Mon, 19 Feb 2024 22:01:43 +0800 Subject: [PATCH] Simplified extending pagination pipelines. (#6921) --- .../Core/src/Abstractions/SetState.cs | 2 +- .../src/Abstractions/WellKnownContextData.cs | 5 +++++ .../CursorPagingArguments.cs | 6 +++-- .../CursorPagingHandler.cs | 12 +++++++--- .../OffsetPagingHandler.cs | 11 +++++++--- .../Core/src/Types/SchemaBuilder.cs | 22 +++++++++++++++++++ .../Types/Types/Pagination/IPagingHandler.cs | 11 ++++++++++ .../Types/Types/Pagination/PagingHelper.cs | 10 +++++---- .../Types/Pagination/PagingMiddleware.cs | 4 +++- .../QueryableCursorPagingProviderTests.cs | 22 ++++++++++++++----- 10 files changed, 86 insertions(+), 19 deletions(-) diff --git a/src/HotChocolate/Core/src/Abstractions/SetState.cs b/src/HotChocolate/Core/src/Abstractions/SetState.cs index 6113b5fac7d..5cfb25507c2 100644 --- a/src/HotChocolate/Core/src/Abstractions/SetState.cs +++ b/src/HotChocolate/Core/src/Abstractions/SetState.cs @@ -1,5 +1,5 @@ namespace HotChocolate; -public delegate void SetState(T value); +public delegate void SetState(T value); public delegate void SetState(object value); diff --git a/src/HotChocolate/Core/src/Abstractions/WellKnownContextData.cs b/src/HotChocolate/Core/src/Abstractions/WellKnownContextData.cs index 3a35ea80ce5..421e741b84f 100644 --- a/src/HotChocolate/Core/src/Abstractions/WellKnownContextData.cs +++ b/src/HotChocolate/Core/src/Abstractions/WellKnownContextData.cs @@ -289,4 +289,9 @@ public static class WellKnownContextData /// Type key to access the internal schema options. /// public const string InternalSchemaOptions = "HotChocolate.Types.InternalSchemaOptions"; + + /// + /// Type key to access the paging arguments in the local resolver state. + /// + public const string PagingArguments = "HotChocolate.Types.PagingArguments"; } diff --git a/src/HotChocolate/Core/src/Types.CursorPagination/CursorPagingArguments.cs b/src/HotChocolate/Core/src/Types.CursorPagination/CursorPagingArguments.cs index 1c4b1a0b704..8d3183d21dd 100644 --- a/src/HotChocolate/Core/src/Types.CursorPagination/CursorPagingArguments.cs +++ b/src/HotChocolate/Core/src/Types.CursorPagination/CursorPagingArguments.cs @@ -1,12 +1,14 @@ -#nullable enable - namespace HotChocolate.Types.Pagination; /// /// The cursor paging arguments are used to specify the /// paging behavior of the cursor based paging handler. /// +#if NET6_0_OR_GREATER +public readonly record struct CursorPagingArguments +#else public readonly struct CursorPagingArguments +#endif { /// /// Initializes a new instance of . diff --git a/src/HotChocolate/Core/src/Types.CursorPagination/CursorPagingHandler.cs b/src/HotChocolate/Core/src/Types.CursorPagination/CursorPagingHandler.cs index 0813cc29fa5..3f62a7dd66c 100644 --- a/src/HotChocolate/Core/src/Types.CursorPagination/CursorPagingHandler.cs +++ b/src/HotChocolate/Core/src/Types.CursorPagination/CursorPagingHandler.cs @@ -89,9 +89,7 @@ public void ValidateContext(IResolverContext context) } } - async ValueTask IPagingHandler.SliceAsync( - IResolverContext context, - object source) + public void PublishPagingArguments(IResolverContext context) { var first = context.ArgumentValue(CursorPagingArgumentNames.First); var last = AllowBackwardPagination @@ -110,7 +108,15 @@ async ValueTask IPagingHandler.SliceAsync( AllowBackwardPagination ? context.ArgumentValue(CursorPagingArgumentNames.Before) : null); + + context.SetLocalState(WellKnownContextData.PagingArguments, arguments); + } + async ValueTask IPagingHandler.SliceAsync( + IResolverContext context, + object source) + { + var arguments = context.GetLocalState(WellKnownContextData.PagingArguments); return await SliceAsync(context, source, arguments).ConfigureAwait(false); } diff --git a/src/HotChocolate/Core/src/Types.OffsetPagination/OffsetPagingHandler.cs b/src/HotChocolate/Core/src/Types.OffsetPagination/OffsetPagingHandler.cs index 80f56fbb9d7..2db644ee0ad 100644 --- a/src/HotChocolate/Core/src/Types.OffsetPagination/OffsetPagingHandler.cs +++ b/src/HotChocolate/Core/src/Types.OffsetPagination/OffsetPagingHandler.cs @@ -76,14 +76,19 @@ public void ValidateContext(IResolverContext context) } } - async ValueTask IPagingHandler.SliceAsync( - IResolverContext context, - object source) + public void PublishPagingArguments(IResolverContext context) { var skip = context.ArgumentValue(OffsetPagingArgumentNames.Skip); var take = context.ArgumentValue(OffsetPagingArgumentNames.Take); var arguments = new OffsetPagingArguments(skip, take ?? DefaultPageSize); + context.SetLocalState(WellKnownContextData.PagingArguments, arguments); + } + async ValueTask IPagingHandler.SliceAsync( + IResolverContext context, + object source) + { + var arguments =context.GetLocalState(WellKnownContextData.PagingArguments); return await SliceAsync(context, source, arguments).ConfigureAwait(false); } diff --git a/src/HotChocolate/Core/src/Types/SchemaBuilder.cs b/src/HotChocolate/Core/src/Types/SchemaBuilder.cs index 6a6ed0854b3..43e3450d759 100644 --- a/src/HotChocolate/Core/src/Types/SchemaBuilder.cs +++ b/src/HotChocolate/Core/src/Types/SchemaBuilder.cs @@ -7,8 +7,10 @@ using HotChocolate.Resolvers; using HotChocolate.Types; using HotChocolate.Types.Descriptors; +using HotChocolate.Types.Descriptors.Definitions; using HotChocolate.Types.Interceptors; using HotChocolate.Types.Introspection; +using HotChocolate.Types.Pagination; using HotChocolate.Utilities; #nullable enable @@ -451,4 +453,24 @@ public ISchemaBuilder TryAddTypeInterceptor(TypeInterceptor interceptor) /// Returns a new instance of . /// public static SchemaBuilder New() => new(); + + private sealed class CopyOptions : TypeInterceptor + { + public override void OnBeforeCompleteType(ITypeCompletionContext completionContext, DefinitionBase definition) + { + if (definition is SchemaTypeDefinition schemaDef) + { + var key = typeof(PagingOptions).FullName!; + + if (completionContext.DescriptorContext.ContextData.TryGetValue(key, out var value)) + { + schemaDef.ContextData[key] = value; + } + else + { + schemaDef.ContextData[key] = new PagingOptions(); + } + } + } + } } diff --git a/src/HotChocolate/Core/src/Types/Types/Pagination/IPagingHandler.cs b/src/HotChocolate/Core/src/Types/Types/Pagination/IPagingHandler.cs index 394bfbe7485..03c269d03d9 100644 --- a/src/HotChocolate/Core/src/Types/Types/Pagination/IPagingHandler.cs +++ b/src/HotChocolate/Core/src/Types/Types/Pagination/IPagingHandler.cs @@ -5,6 +5,9 @@ namespace HotChocolate.Types.Pagination; +/// +/// The paging handler is used in the paging middleware to abstract the actual paging logic. +/// public interface IPagingHandler { /// @@ -19,6 +22,14 @@ public interface IPagingHandler /// void ValidateContext(IResolverContext context); + /// + /// Publish Paging Arguments to local state. + /// + /// + /// The current resolver context. + /// + void PublishPagingArguments(IResolverContext context); + /// /// Slices the and returns a page from it. /// diff --git a/src/HotChocolate/Core/src/Types/Types/Pagination/PagingHelper.cs b/src/HotChocolate/Core/src/Types/Types/Pagination/PagingHelper.cs index da48ee12bfd..52f6d023677 100644 --- a/src/HotChocolate/Core/src/Types/Types/Pagination/PagingHelper.cs +++ b/src/HotChocolate/Core/src/Types/Types/Pagination/PagingHelper.cs @@ -107,10 +107,12 @@ IExtendedType ResolveType() } private static FieldMiddleware CreateMiddleware( - IPagingHandler handler) => - FieldClassMiddlewareFactory.Create( - typeof(PagingMiddleware), - (typeof(IPagingHandler), handler)); + IPagingHandler handler) + => next => + { + var middleware = new PagingMiddleware(next, handler); + return context => middleware.InvokeAsync(context); + }; public static IExtendedType GetSchemaType( IDescriptorContext context, diff --git a/src/HotChocolate/Core/src/Types/Types/Pagination/PagingMiddleware.cs b/src/HotChocolate/Core/src/Types/Types/Pagination/PagingMiddleware.cs index 9ac5912d877..9109e5f574a 100644 --- a/src/HotChocolate/Core/src/Types/Types/Pagination/PagingMiddleware.cs +++ b/src/HotChocolate/Core/src/Types/Types/Pagination/PagingMiddleware.cs @@ -13,12 +13,14 @@ public class PagingMiddleware(FieldDelegate next, IPagingHandler pagingHandler) private readonly IPagingHandler _pagingHandler = pagingHandler ?? throw new ArgumentNullException(nameof(pagingHandler)); - public async Task InvokeAsync(IMiddlewareContext context) + public async ValueTask InvokeAsync(IMiddlewareContext context) { _pagingHandler.ValidateContext(context); + _pagingHandler.PublishPagingArguments(context); await _next(context).ConfigureAwait(false); + // if the result is a field result we gonna unwrap it. if (context.Result is IFieldResult fieldResult) { if (fieldResult.IsError) diff --git a/src/HotChocolate/Core/test/Types.CursorPagination.Tests/QueryableCursorPagingProviderTests.cs b/src/HotChocolate/Core/test/Types.CursorPagination.Tests/QueryableCursorPagingProviderTests.cs index b9c6daaf5ec..93dd5ac687b 100644 --- a/src/HotChocolate/Core/test/Types.CursorPagination.Tests/QueryableCursorPagingProviderTests.cs +++ b/src/HotChocolate/Core/test/Types.CursorPagination.Tests/QueryableCursorPagingProviderTests.cs @@ -34,6 +34,7 @@ public async Task TakeFirst() var context = new MockContext(pagingDetails); // act + pagingHandler.PublishPagingArguments(context); var connection = (Connection)await pagingHandler.SliceAsync(context, list); // assert @@ -75,6 +76,7 @@ public async Task TakeLastSingle() var context = new MockContext(pagingDetails); // act + pagingHandler.PublishPagingArguments(context); var connection = (Connection)await pagingHandler.SliceAsync(context, list); // assert @@ -110,6 +112,7 @@ public async Task TakeLast() var context = new MockContext(pagingDetails); // act + pagingHandler.PublishPagingArguments(context); var connection = (Connection)await pagingHandler.SliceAsync(context, list); // assert @@ -148,12 +151,14 @@ public async Task TakeFirstAfter() var pagingDetails = new CursorPagingArguments(); var context = new MockContext(pagingDetails); + pagingHandler.PublishPagingArguments(context); var connection = (Connection)await pagingHandler.SliceAsync(context, list); pagingDetails = new CursorPagingArguments(after: connection.Info.StartCursor, first: 2); context = new MockContext(pagingDetails); // act + pagingHandler.PublishPagingArguments(context); connection = (Connection)await pagingHandler.SliceAsync(context, list); // assert @@ -192,12 +197,14 @@ public async Task TakeLastBefore() var pagingDetails = new CursorPagingArguments(first: 5); var context = new MockContext(pagingDetails); + pagingHandler.PublishPagingArguments(context); var connection = (Connection)await pagingHandler.SliceAsync(context, list); pagingDetails = new CursorPagingArguments(before: connection.Info.EndCursor, last: 2); context = new MockContext(pagingDetails); // act + pagingHandler.PublishPagingArguments(context); connection = (Connection)await pagingHandler.SliceAsync(context, list); // assert @@ -238,6 +245,7 @@ public async Task HasNextPage_True() var context = new MockContext(pagingDetails); // act + pagingHandler.PublishPagingArguments(context); var connection = (Connection)await pagingHandler.SliceAsync(context, list); // assert @@ -260,6 +268,7 @@ public async Task HasNextPage_False() var context = new MockContext(pagingDetails); // act + pagingHandler.PublishPagingArguments(context); var connection = (Connection)await pagingHandler.SliceAsync(context, list); // assert @@ -280,12 +289,15 @@ public async Task HasPrevious_True() var pagingDetails = new CursorPagingArguments(first: 1); var context = new MockContext(pagingDetails); + + pagingHandler.PublishPagingArguments(context); var connection = (Connection)await pagingHandler.SliceAsync(context, list); pagingDetails = new CursorPagingArguments(after: connection.Info.EndCursor, first: 2); context = new MockContext(pagingDetails); // act + pagingHandler.PublishPagingArguments(context); connection = (Connection)await pagingHandler.SliceAsync(context, list); // assert @@ -308,6 +320,7 @@ public async Task HasPrevious_False() var context = new MockContext(pagingDetails); // act + pagingHandler.PublishPagingArguments(context); var connection = (Connection)await pagingHandler.SliceAsync(context, list); // assert @@ -339,6 +352,7 @@ public async Task Executable() var context = new MockContext(pagingDetails); // act + pagingHandler.PublishPagingArguments(context); var connection = (Connection)await pagingHandler.SliceAsync(context, list); // assert @@ -388,6 +402,7 @@ public async Task Executable_Queryable() var context = new MockContext(pagingDetails); // act + pagingHandler.PublishPagingArguments(context); var connection = (Connection)await pagingHandler.SliceAsync(context, list); // assert @@ -444,11 +459,8 @@ public IServiceProvider Services set => throw new NotImplementedException(); } - public IImmutableDictionary LocalContextData - { - get => throw new NotImplementedException(); - set => throw new NotImplementedException(); - } + public IImmutableDictionary LocalContextData { get; set; } = + ImmutableDictionary.Empty; public CancellationToken RequestAborted => default;