private static CursorPagingRange SliceRange( CursorPagingArguments arguments, int maxElementCount) { // [SPEC] if after is set then remove all elements of edges before and including // afterEdge. // // The cursor is increased by one so that the index points to the element after var startIndex = arguments.After is { } a ? IndexEdge <TEntity> .DeserializeCursor(a) + 1 : 0; // [SPEC] if before is set then remove all elements of edges before and including // beforeEdge. var before = arguments.Before is { } b ? IndexEdge <TEntity> .DeserializeCursor(b) : maxElementCount; // if after is negative we have know how much of the offset was in the negative range. // The amount of positions that are in the negative range, have to be subtracted from // the take or we will fetch too many items. var startOffsetCorrection = 0; if (startIndex < 0) { startOffsetCorrection = Math.Abs(startIndex); startIndex = 0; } CursorPagingRange range = new(startIndex, before); //[SPEC] If first is less than 0 throw an error ValidateFirst(arguments, out var first); if (first is not null) { first -= startOffsetCorrection; if (first < 0) { first = 0; } } //[SPEC] Slice edges to be of length first by removing edges from the end of edges. range.Take(first); //[SPEC] if last is less than 0 throw an error ValidateLast(arguments, out var last); //[SPEC] Slice edges to be of length last by removing edges from the start of edges. range.TakeLast(last); return(range); }
public static ICursorPageSlice <T> SliceAsCursorPage <T>(this IEnumerable <T> items, string?after, int?first, string?before, int?last) where T : class { //Do nothing if there are no results... if (!items.Any()) { return(new CursorPageSlice <T>(Enumerable.Empty <ICursorResult <T> >(), 0, false, false)); } var afterIndex = after != null ? IndexEdge <string> .DeserializeCursor(after) : 0; var beforeIndex = before != null ? IndexEdge <string> .DeserializeCursor(before) : 0; //FIRST log the index of all items in the list BEFORE slicing, as these indexes are // the Cursor Indexes for paging up/down the entire list, & ICursorResult is the Decorator // around the Entity Models. //NOTE: We MUST materialize this after applying index values to prevent ongoing increments... int index = 0; IEnumerable <ICursorResult <T> > slice = items .Select(c => new CursorResult <T>(c, ++index)) .ToList(); int totalCount = slice.Count(); //If After specified, remove all before After (or skip past After) if (afterIndex > 0 && slice.Last().CursorIndex > afterIndex) { slice = slice.Skip(afterIndex); } //If Before is specified, remove all after Before (Skip Until Before is reached) if (beforeIndex > 0 && slice.Last().CursorIndex > beforeIndex) { slice = slice.SkipWhile(c => c.CursorIndex < beforeIndex); } //If First is specified, then take the first/top rows from the current Slice! if (first.HasValue && first > 0 && slice.Count() > first) { slice = slice.Take(first.Value); } //If First is specified, then take the first/top rows from the current Slice! if (last.HasValue && last > 0 && slice.Count() > last) { slice = slice.TakeLast(last.Value); } //Wrap all results into a PagedCursor Slice result wit Total Count... //NOTE: to ensure our pagination is complete, we materialize the Results! var results = slice.ToList(); var firstCursor = results.FirstOrDefault(); var lastCursor = results.LastOrDefault(); var cursorPageSlice = new CursorPageSlice <T>( results, totalCount, hasPreviousPage: firstCursor?.CursorIndex > 1, hasNextPage: lastCursor?.CursorIndex < totalCount ); return(cursorPageSlice); }