packages feed

yesod-page-cursor 1.0.0.1 → 2.0.0.0

raw patch · 6 files changed

+73/−51 lines, 6 filesnew-uploaderPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Yesod.Page: withPage :: (MonadHandler m, ToJSON position, FromJSON position, RenderRoute (HandlerSite m)) => (a -> position) -> (Cursor position -> m [a]) -> m (Page a)
+ Yesod.Page: withPage :: (MonadHandler m, ToJSON position, FromJSON position, RenderRoute (HandlerSite m)) => Int -> (a -> position) -> (Cursor position -> m [a]) -> m (Page a)
- Yesod.Page: withPageLink :: (MonadHandler m, ToJSON position, FromJSON position, RenderRoute (HandlerSite m)) => (a -> position) -> (Cursor position -> m [a]) -> m [a]
+ Yesod.Page: withPageLink :: (MonadHandler m, ToJSON position, FromJSON position, RenderRoute (HandlerSite m)) => Int -> (a -> position) -> (Cursor position -> m [a]) -> m [a]

Files

ChangeLog.md view
@@ -4,6 +4,11 @@  None. +## [v2.0.0.0](https://github.com/freckle/yesod-page-cursor/compare/v1.0.0.1...v2.0.0.0)++- Add `defaultLimit :: Int` argument to `withPage` and `withPageLink`++ ## [v1.0.0.1](https://github.com/freckle/yesod-page-cursor/compare/v1.0.0.0...v1.0.0.1)  - Fix missing `previous` link in all but last page
README.md view
@@ -10,7 +10,7 @@   let     parseParams =       (,) <$> Param.required "teacherId" <*> Param.optional "courseId"-  page <- withPage entityPage parseParams $ \Cursor {..} -> do+  page <- withPage 100 entityPage parseParams $ \Cursor {..} -> do     let (teacherId, mCourseId) = cursorParams     fmap (sort cursorPosition) . runDB $ selectList       (catMaybes@@ -39,7 +39,8 @@     Last -> reverse ``` -`cursorLastPosition` is configurable. A page sorted by `created_at` may look like:+`cursorLastPosition` is configurable. A page sorted by `created_at` may look+like:  ```hs createdAtPage = PageConfig@@ -51,7 +52,7 @@ getSortedSomeR :: Handler Value getSortedSomeR = do   let parseParams = pure ()-  page <- withPage createdAtPage parseParams $ \Cursor {..} -> do+  page <- withPage 100 createdAtPage parseParams $ \Cursor {..} -> do     fmap (sort cursorPosition) . runDB $ selectList       (whereClause cursorPosition)       [ LimitTo $ fromMaybe 100 cursorLimit@@ -84,7 +85,8 @@  ## Usage -Paginated requests return a single page and a link with a cursor token to retrieve the next page.+Paginated requests return a single page and a link with a cursor token to+retrieve the next page.  ```sh $ curl 'some-rest.com/endpoint?limit=3'
src/Yesod/Page.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE LambdaCase #-}@@ -21,7 +22,7 @@ import Data.Aeson import qualified Data.ByteString.Lazy as BSL import Data.Foldable (asum)-import Data.Maybe (catMaybes, fromMaybe)+import Data.Maybe (catMaybes) import Data.Text (Text, pack, unpack) import Data.Text.Encoding (decodeUtf8, encodeUtf8) import Network.HTTP.Link (writeLinkHeader)@@ -43,11 +44,21 @@      , FromJSON position      , RenderRoute (HandlerSite m)      )-  => (a -> position)+  => Int+  -- ^ Default limit if not specified in the @'Cursor'@+  --+  -- Must be a positive natural number.+  --+  -> (a -> position)+  -- ^ How to get an item's position+  --+  -- For example, this would be @'entityKey'@ for paginated @'Entity'@ values.+  --   -> (Cursor position -> m [a])+  -- ^ How to fetch one page of data at the given @'Cursor'@   -> m [a]-withPageLink makePosition fetchItems = do-  page <- withPage makePosition fetchItems+withPageLink defaultLimit makePosition fetchItems = do+  page <- withPage defaultLimit makePosition fetchItems    let     link = writeLinkHeader $ catMaybes@@ -65,7 +76,12 @@      , FromJSON position      , RenderRoute (HandlerSite m)      )-  => (a -> position)+  => Int+  -- ^ Default limit if not specified in the @'Cursor'@+  --+  -- Must be a positive natural number.+  --+  -> (a -> position)   -- ^ How to get an item's position   --   -- For example, this would be @'entityKey'@ for paginated @'Entity'@ values.@@ -73,8 +89,8 @@   -> (Cursor position -> m [a])   -- ^ How to fetch one page of data at the given @'Cursor'@   -> m (Page a)-withPage makePosition fetchItems = do-  cursor <- parseCursorParams+withPage defaultLimit makePosition fetchItems = do+  cursor <- parseCursorParams defaultLimit    -- We have to fetch page-size+1 items to know if there is a next page or not   let (Limit realLimit) = cursorLimit cursor@@ -107,17 +123,11 @@     , pageNext = do       guard hasNextLink       item <- lastMay page-      pure-        $ cursorRouteAtPosition cursor-        $ Next-        $ makePosition item+      pure $ cursorRouteAtPosition cursor $ Next $ makePosition item     , pagePrevious = do       guard hasPreviousLink       item <- headMay page-      pure-        $ cursorRouteAtPosition cursor-        $ Previous-        $ makePosition item+      pure $ cursorRouteAtPosition cursor $ Previous $ makePosition item     , pageLast = cursorRouteAtPosition cursor Last     } @@ -159,7 +169,7 @@ instance ToJSON position => ToJSON (Position position) where   toJSON = \case     First -> String "first"-    Next p -> object ["next" .= p ]+    Next p -> object ["next" .= p]     Previous p -> object ["previous" .= p]     Last -> String "last" @@ -167,16 +177,13 @@   parseJSON = \case     Null -> pure First     String t -> case t of-        "first" -> pure First-        "last" -> pure Last-        _ -> invalidPosition+      "first" -> pure First+      "last" -> pure Last+      _ -> invalidPosition     Object o -> do-        mNext <- o .:? "next"-        mPrevious <- o .:? "previous"-        maybe invalidPosition pure $ asum-         [ Next <$> mNext-         , Previous <$> mPrevious-         ]+      mNext <- o .:? "next"+      mPrevious <- o .:? "previous"+      maybe invalidPosition pure $ asum [Next <$> mNext, Previous <$> mPrevious]      _ -> invalidPosition    where@@ -187,23 +194,28 @@  newtype Limit = Limit { unLimit :: Int } +validateLimit :: Int -> Either String Limit+validateLimit limit+  | limit <= 0 = badLimit limit+  | otherwise = Right $ Limit limit+ readLimit :: Text -> Either String Limit-readLimit t = case readMaybe @Int $ unpack t of-    Nothing -> limitMustBe "an integer"-    Just limit | limit <= 0 -> limitMustBe "positive and non-zero"-    Just limit -> Right $ Limit limit-  where-    limitMustBe msg = Left $ "Limit must be " <> msg <> ": " <> show t+readLimit t = maybe (badLimit t) validateLimit $ readMaybe @Int $ unpack t +badLimit :: Show a => a -> Either String x+badLimit a = Left $ "Limit must be a positive natural number: " <> show a+ cursorRouteAtPosition   :: ToJSON position => Cursor position -> Position position -> RenderedRoute cursorRouteAtPosition cursor position =-  updateQueryParameter "position" (Just $ encodeText position) $ cursorRoute cursor+  updateQueryParameter "position" (Just $ encodeText position)+    $ cursorRoute cursor  parseCursorParams   :: (MonadHandler m, FromJSON position, RenderRoute (HandlerSite m))-  => m (Cursor position)-parseCursorParams = do+  => Int+  -> m (Cursor position)+parseCursorParams defaultLimit = do   mePosition <- fmap eitherDecodeText <$> lookupGetParam "position"   position <- case mePosition of     Nothing -> pure First@@ -211,10 +223,9 @@     Just (Right p) -> pure p    limit <--    either (\e -> invalidArgs [pack e]) pure-        . readLimit-        . fromMaybe "100"-        =<< lookupGetParam "limit"+    either (invalidArgs . pure . pack) pure+    . maybe (validateLimit defaultLimit) readLimit+    =<< lookupGetParam "limit"    renderedRoute <- getRenderedRoute   pure $ Cursor renderedRoute position limit@@ -227,14 +238,15 @@  headMay :: [a] -> Maybe a headMay [] = Nothing-headMay (x:_) = Just x+headMay (x : _) = Just x  lastMay :: [a] -> Maybe a lastMay [] = Nothing lastMay [x] = Just x-lastMay (_:xs) = lastMay xs+lastMay (_ : xs) = lastMay xs  takeEnd :: Int -> [a] -> [a] takeEnd i xs = f xs (drop i xs)-    where f (_:xs') (_:ys) = f xs' ys-          f xs' _ = xs'+ where+  f (_ : xs') (_ : ys) = f xs' ys+  f xs' _ = xs'
test/Spec.hs view
@@ -41,7 +41,7 @@       getPaginated SomeR [("teacherId", "1"), ("limit", "-1")]        statusIs 400-      bodyContains "must be positive and non-zero"+      bodyContains "must be a positive natural number"      it "returns no cursor when there are no items" $ do       getPaginated SomeR [("teacherId", "1")]
test/TestApp.hs view
@@ -97,7 +97,10 @@ getSomeLinkR = makePaginationRoute withPageLink  type Pagination m f a-  = (Entity a -> Key a) -> (Cursor (Key a) -> m [Entity a]) -> m (f (Entity a))+  = Int+  -> (Entity a -> Key a)+  -> (Cursor (Key a) -> m [Entity a])+  -> m (f (Entity a))  makePaginationRoute   :: (Functor f, ToJSON (f Value))@@ -107,7 +110,7 @@   teacherId <- requireParam "teacherId"   mCourseId <- optionalParam "courseId" -  items <- withPage' entityKey $ \Cursor {..} ->+  items <- withPage' 100 entityKey $ \Cursor {..} ->     runDB $ sort cursorPosition <$> selectList       (catMaybes         [ Just $ SomeAssignmentTeacherId ==. teacherId
yesod-page-cursor.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: b4c5d8ba1fe208184ea08c81eabd470f9b93ba740986c4eb430c742098ddbde4+-- hash: 8dc3f8214e380e14ac9e103fd4cdff50f3186e9497536d2f654257e1e1d1072f  name:           yesod-page-cursor-version:        1.0.0.1+version:        2.0.0.0 description:    Cursor based pagination for Yesod homepage:       https://github.com/freckle/yesod-page-cursor#readme bug-reports:    https://github.com/freckle/yesod-page-cursor/issues