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);
        }
Ejemplo n.º 4
0
        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;
 }
Ejemplo n.º 6
0
 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);