packages feed

relay-pagination-0.1.0.0: src/Relay/Pagination/Request.hs

-- | Validation of the four Relay request arguments (first/after/last/before)
-- into a 'PageRequest'. Strict subset of the Relay spec: first+last together
-- is rejected rather than tolerated, and page sizes above the endpoint's
-- maximum are rejected rather than clamped.
module Relay.Pagination.Request
  ( Direction (..),
    PageConfig (..),
    PageRequest (..),
    PageRequestError (..),
    mkPageRequest,
  )
where

import Relay.Pagination.Cursor (Cursor)

-- | Which way a page walks the canonical sort order. Forward serves
-- first/after; Backward serves last/before. Edges are returned in canonical
-- order either way (the SQL layer reverses backward pages in memory).
data Direction = Forward | Backward
  deriving stock (Eq, Show)

-- | Per-endpoint pagination policy, written once by the service author.
-- Precondition (unchecked): 0 < defaultPageSize <= maxPageSize.
data PageConfig = PageConfig
  { defaultPageSize :: !Int,
    maxPageSize :: !Int
  }
  deriving stock (Eq, Show)

-- | A validated page request: how many rows, which direction, from where.
-- 'cursor' is Nothing for the first page in the given direction.
data PageRequest = PageRequest
  { pageSize :: !Int,
    direction :: !Direction,
    cursor :: !(Maybe Cursor)
  }
  deriving stock (Eq, Show)

-- | Why a combination of first/after/last/before was rejected. Checked in
-- declaration order; the first applicable error wins.
data PageRequestError
  = FirstAndLastBothGiven
  | AfterAndBeforeBothGiven
  | FirstWithBefore
  | LastWithAfter
  | NegativePageSize !Int
  | PageSizeTooLarge
      { requested :: !Int,
        allowedMax :: !Int
      }
  deriving stock (Eq, Show)

-- | Validate the four Relay arguments, in the order they appear in a query
-- string: @first@, @after@, @last@, @before@.
mkPageRequest ::
  PageConfig ->
  -- | @first@
  Maybe Int ->
  -- | @after@
  Maybe Cursor ->
  -- | @last@
  Maybe Int ->
  -- | @before@
  Maybe Cursor ->
  Either PageRequestError PageRequest
mkPageRequest config mFirst mAfter mLast mBefore
  | Just _ <- mFirst, Just _ <- mLast = Left FirstAndLastBothGiven
  | Just _ <- mAfter, Just _ <- mBefore = Left AfterAndBeforeBothGiven
  | Just _ <- mFirst, Just _ <- mBefore = Left FirstWithBefore
  | Just _ <- mLast, Just _ <- mAfter = Left LastWithAfter
  | Just n <- mFirst = do
      size <- checkedSize n
      Right PageRequest {pageSize = size, direction = Forward, cursor = mAfter}
  | Just n <- mLast = do
      size <- checkedSize n
      Right PageRequest {pageSize = size, direction = Backward, cursor = mBefore}
  | Just _ <- mBefore =
      Right PageRequest {pageSize = defaultPageSize config, direction = Backward, cursor = mBefore}
  | otherwise =
      Right PageRequest {pageSize = defaultPageSize config, direction = Forward, cursor = mAfter}
  where
    checkedSize n
      | n < 0 = Left (NegativePageSize n)
      | n > maxPageSize config = Left PageSizeTooLarge {requested = n, allowedMax = maxPageSize config}
      | otherwise = Right n