packages feed

relay-pagination-conformance-0.1.0.0: test/Oracle.hs

-- | A known-good, pure Relay paginator over an in-memory canonical list —
-- the executable form of the PageInfo semantics table. The walker and
-- checker are tested against it before they are allowed to judge anything.
--
-- Cursors are a stable per-row token (the row's unique key rendered to
-- bytes), never a positional offset.
module Oracle (oraclePaginate) where

import Data.ByteString (ByteString)
import Data.Maybe (isJust)
import Relay.Pagination

-- | Pure Relay pagination over @rows@, which must be the full result set in
-- canonical order. Forward with no cursor: the first @pageSize@ rows; with a
-- cursor: the @pageSize@ rows after the matching row. Backward mirrors it
-- from the end, always preserving canonical order within the page.
-- Boundary flags follow the PageInfo semantics table: the walk-direction
-- flag reflects whether more rows exist beyond the slice, the opposite flag
-- reflects whether a cursor was provided.
oraclePaginate ::
  (row -> ByteString) ->
  [row] ->
  PageRequest ->
  Connection row
oraclePaginate cursorOf rows PageRequest {pageSize = size, direction = dir, cursor = mCursor} =
  case dir of
    Forward ->
      let rest = case mCursor of
            Nothing -> rows
            Just c -> drop 1 (dropWhile (\r -> Cursor (cursorOf r) /= c) rows)
          slice = take size rest
          probeExists = length rest > size
       in connectionOf slice probeExists (isJust mCursor)
    Backward ->
      let preceding = case mCursor of
            Nothing -> rows
            Just c -> takeWhile (\r -> Cursor (cursorOf r) /= c) rows
          slice = lastN size preceding
          probeExists = length preceding > size
       in connectionOf slice (isJust mCursor) probeExists
  where
    connectionOf slice nextFlag prevFlag =
      Connection
        { edges = [Edge {node = r, cursor = Cursor (cursorOf r)} | r <- slice],
          pageInfo =
            PageInfo
              { hasNextPage = nextFlag,
                hasPreviousPage = prevFlag,
                startCursor = case slice of
                  r : _ -> Just (Cursor (cursorOf r))
                  [] -> Nothing,
                endCursor = case reverse slice of
                  r : _ -> Just (Cursor (cursorOf r))
                  [] -> Nothing
              }
        }

    lastN n xs = drop (max 0 (length xs - n)) xs