private async ValueTask <Connection> ResolveAsync( IQueryable <TEntity> source, CursorPagingArguments arguments = default, CancellationToken cancellationToken = default) { var count = await Task.Run(source.Count, cancellationToken) .ConfigureAwait(false); int?after = arguments.After is { } a ? (int?)IndexEdge <TEntity> .DeserializeCursor(a) : null; int?before = arguments.Before is { } b ? (int?)IndexEdge <TEntity> .DeserializeCursor(b) : null; IReadOnlyList <IndexEdge <TEntity> > selectedEdges = await GetSelectedEdgesAsync( source, arguments.First, arguments.Last, after, before, cancellationToken) .ConfigureAwait(false); IndexEdge <TEntity>?firstEdge = selectedEdges.Count == 0 ? null : selectedEdges[0]; IndexEdge <TEntity>?lastEdge = selectedEdges.Count == 0 ? null : selectedEdges[selectedEdges.Count - 1]; var pageInfo = new ConnectionPageInfo( lastEdge?.Index < count - 1, firstEdge?.Index > 0, firstEdge?.Cursor, lastEdge?.Cursor, count); return(new Connection <TEntity>( selectedEdges, pageInfo, ct => new ValueTask <int>(pageInfo.TotalCount ?? 0))); }
public async Task TakeFirst() { // arrange var typeInspector = new DefaultTypeInspector(); IExtendedType sourceType = typeInspector.GetType(typeof(List <string>)); IPagingProvider pagingProvider = new QueryableCursorPagingProvider(); IPagingHandler pagingHandler = pagingProvider.CreateHandler(sourceType, default); var list = new List <string> { "a", "b", "c", "d", "e", "f", "g" }; var pagingDetails = new CursorPagingArguments(2); var context = new MockContext(pagingDetails); // act var connection = (Connection)await pagingHandler.SliceAsync(context, list); // assert Assert.Collection(connection.Edges, t => { Assert.Equal("a", t.Node); Assert.Equal(0, GetPositionFromCursor(t.Cursor)); }, t => { Assert.Equal("b", t.Node); Assert.Equal(1, GetPositionFromCursor(t.Cursor)); }); Assert.False( connection.Info.HasPreviousPage, "HasPreviousPage"); Assert.True( connection.Info.HasNextPage, "HasNextPage"); }
public async Task HasPrevious_False() { // arrange var typeInspector = new DefaultTypeInspector(); IExtendedType sourceType = typeInspector.GetType(typeof(List <string>)); IPagingProvider pagingProvider = new QueryableCursorPagingProvider(); IPagingHandler pagingHandler = pagingProvider.CreateHandler(sourceType, default); var list = new List <string> { "a", "b", "c", "d", "e", "f", "g" }; var pagingDetails = new CursorPagingArguments(first: 1); var context = new MockContext(pagingDetails); // act var connection = (Connection)await pagingHandler.SliceAsync(context, list); // assert Assert.False(connection.Info.HasPreviousPage); }
async ValueTask <IPage> IPagingHandler.SliceAsync( IResolverContext context, object source) { var first = context.ArgumentValue <int?>(CursorPagingArgumentNames.First); var last = AllowBackwardPagination ? context.ArgumentValue <int?>(CursorPagingArgumentNames.Last) : null; if (first is null && last is null) { first = DefaultPageSize; } var arguments = new CursorPagingArguments( first, last, context.ArgumentValue <string?>(CursorPagingArgumentNames.After), AllowBackwardPagination ? context.ArgumentValue <string?>(CursorPagingArgumentNames.Before) : null); return(await SliceAsync(context, source, arguments).ConfigureAwait(false)); }
public MockContext(CursorPagingArguments arguments) { _arguments = arguments; }
protected abstract ValueTask <Connection> SliceAsync( IResolverContext context, object source, CursorPagingArguments arguments);
/// <summary> /// Applies the pagination algorithm to the provided query. /// </summary> /// <param name="query">The query builder.</param> /// <param name="arguments">The paging arguments.</param> /// <param name="totalCount">Specify the total amount of elements</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns> /// Returns the connection. /// </returns> public async ValueTask <Connection <TEntity> > ApplyPaginationAsync( TQuery query, CursorPagingArguments arguments, int?totalCount, CancellationToken cancellationToken) { if (query is null) { throw new ArgumentNullException(nameof(query)); } var maxElementCount = int.MaxValue; Func <CancellationToken, ValueTask <int> > executeCount = totalCount is null ? ct => CountAsync(query, ct) : _ => new ValueTask <int>(totalCount.Value); // We only need the maximal element count if no `before` counter is set and no `first` // argument is provided. if (arguments.Before is null && arguments.First is null) { var count = await executeCount(cancellationToken); maxElementCount = count; // in case we already know the total count, we override the countAsync parameter // so that we do not have to fetch the count twice executeCount = _ => new ValueTask <int>(count); } CursorPagingRange range = SliceRange(arguments, maxElementCount); var skip = range.Start; var take = range.Count(); // we fetch one element more than we requested if (take != maxElementCount) { take++; } TQuery slicedSource = query; if (skip != 0) { slicedSource = ApplySkip(query, skip); } if (take != maxElementCount) { slicedSource = ApplyTake(slicedSource, take); } IReadOnlyList <Edge <TEntity> > selectedEdges = await ExecuteAsync(slicedSource, skip, cancellationToken); var moreItemsReturnedThanRequested = selectedEdges.Count > range.Count(); var isSequenceFromStart = range.Start == 0; selectedEdges = new SkipLastCollection <Edge <TEntity> >( selectedEdges, moreItemsReturnedThanRequested); ConnectionPageInfo pageInfo = CreatePageInfo(isSequenceFromStart, moreItemsReturnedThanRequested, selectedEdges); return(new Connection <TEntity>(selectedEdges, pageInfo, executeCount)); }
/// <summary> /// Applies the pagination algorithm to the provided query. /// </summary> /// <param name="query">The query builder.</param> /// <param name="arguments">The paging arguments.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns> /// Returns the connection. /// </returns> public ValueTask <Connection <TEntity> > ApplyPaginationAsync( TQuery query, CursorPagingArguments arguments, CancellationToken cancellationToken) => ApplyPaginationAsync(query, arguments, null, cancellationToken);