packages feed

servant-pagination 2.1.0 → 2.1.1

raw patch · 3 files changed

+174/−47 lines, 3 filesdep ~servantdep ~servant-serverPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: servant, servant-server

API changes (from Hackage documentation)

Files

CHANGELOG.md view
@@ -1,5 +1,10 @@ # Changelog +## v2.1.1 (2018-07-10)++- Allow servant 0.14++ ## v2.1.0 (2018-04-16)   - Add some tests (QuickCheck round-up & control some Ranges parsing)
README.md view
@@ -8,7 +8,7 @@ enabling a client to consume the API in different fashions (pagination with offset / limit, endless scroll using last referenced resources, ascending and descending ordering, etc.) -Therefore, client may provide a `Range` header with their request with the following format:+Therefore, client can provide a `Range` header with their request with the following format:  - `Range: <field> [<value>][; offset <o>][; limit <l>][; order <asc|desc>]` @@ -17,9 +17,9 @@ created after the fifteenth of January, skipping the first 5.  As a response, the server may return the list of corresponding document, and augment the-response with 3 or 4 headers:+response with 3 headers: -- `Accept-Ranges`: A comma-separated list of field upon which a range can be defined+- `Accept-Ranges`: A comma-separated list of fields upon which a range can be defined - `Content-Range`: Actual range corresponding to the content being returned - `Next-Range`: Indicate what should be the next `Range` header in order to retrieve the next range @@ -30,14 +30,48 @@ - `Next-Range: createdAt 2017-02-19T12:56:28.000Z; offset 0; limit 100; order desc`  -## Getting Starting+## Getting Started -Code-wise, the integration is rather seamless and requires to declare a `Range` type on-on a given field and to provide an instance of `HasPagination` and `FromHttpApiData`. -The `getRangeField` method from `HasPagination` is merely a getter to retrieve-a range's field value from a resource. +Code-wise the integration is quite seamless and unobtrusive. `servant-pagination` provides a+`Ranges (fields :: [Symbol]) (resource :: *) -> *` data-type for declaring available ranges+on a group of _fields_ and a target _resource_. To each combination (resource + field) is+associated a given type `RangeType (resource :: *) (field :: Symbol) -> *` as described by+the type-family in the `HasPagination` type-class. +So, let's start with some imports and extensions to get this out of the way:+ ```hs+{-# LANGUAGE DataKinds             #-}+{-# LANGUAGE DeriveGeneric         #-}+{-# LANGUAGE FlexibleInstances     #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE TypeApplications      #-}+{-# LANGUAGE TypeFamilies          #-}+{-# LANGUAGE TypeOperators         #-}++import           Data.Aeson         (ToJSON, genericToJSON)+import           Data.Maybe         (fromMaybe)+import           Data.Proxy         (Proxy (..))+import           GHC.Generics       (Generic)+import           Servant            ((:>), GetPartialContent, Handler, Header, Headers, JSON, Server)+import           Servant.Pagination (HasPagination (..), PageHeaders, Range (..), Ranges, applyRange)++import qualified Data.Aeson               as Aeson+import qualified Network.Wai.Handler.Warp as Warp+import qualified Servant.Pagination       as Pagination+import qualified Servant+```+++#### Declaring the Resource++Servant APIs are rather resource-oriented, and so is `servant-pagination`. This+guide shows a basic example working with `JSON` (as you could tell from the+import list already). To make the world a <span style='text-decoration:+line-through'>better</span> colored place, let's create an API to retrieve+colors -- with pagination.++```hs data Color = Color   { name :: String   , rgb  :: [Int]@@ -45,52 +79,141 @@   } deriving (Eq, Show, Generic)  instance ToJSON Color where-  toJSON = genericToJSON defaultOptions+  toJSON =+    genericToJSON Aeson.defaultOptions +colors :: [Color]+colors =+  [ Color "Black" [0, 0, 0] "#000000"+  , Color "Blue" [0, 0, 255] "#0000ff"+  , Color "Green" [0, 128, 0] "#008000"+  , Color "Grey" [128, 128, 128] "#808080"+  , Color "Purple" [128, 0, 128] "#800080"+  , Color "Red" [255, 0, 0] "#ff0000"+  , Color "Yellow" [255, 255, 0] "#ffff00"+  ]+```++#### Declaring the Ranges++Now that we have defined our _resource_ (a.k.a `Color`), we are ready to declare a new `Range`+that will operate on a "name" field (genuinely named after the `name` fields from the `Color`+record).+For that, we need to tell `servant-pagination` two things:++- What is the type of the corresponding `Range` values+- How do we get one of these values from our resource++This is done via defining an instance of `HasPagination` as follows:++```hs instance HasPagination Color "name" where   type RangeType Color "name" = String   getFieldValue _ = name+  -- getRangeOptions :: Proxy "name" -> Proxy Color -> RangeOptions+  -- getDefaultRange :: Proxy Color -> Range "name" String++defaultRange :: Range "name" String+defaultRange =+  getDefaultRange (Proxy @Color) ``` -That's it, the range is ready to use and to be declared in the Servant API. Additionally,-this library provides a small type alias helper `PageHeaders` to derive response headers from-a range. For example:+Note that `getFieldValue :: Proxy "name" -> Color -> String` is the minimal complete definintion+of the class. Yet, you can define `getRangeOptions` to provide different parsing options (see+the last section of this guide). In the meantime, we've also defined a `defaultRange` as it will+come in handy when defining our handler. +#### API++Good, we have a resource, we have a `Range` working on that resource, we can now declare our+API using other Servant combinators we already know:+ ```hs type API =   "colors"-  :> Header "Range" (Ranges '["name"] Color)-  :> GetPartialContent '[JSON] (Headers (PageHeaders '["name"] Color) [Color])+    :> Header "Range" (Ranges '["name"] Color)+    :> GetPartialContent '[JSON] (Headers MyHeaders [Color])++type MyHeaders =+  Header "Total-Count" Int :> PageHeaders '["name"] Color ``` -The range is then provided to the corresponding handler as a `Maybe NameRange` (for Servant-<0.13) type and can be used by the backend service to actually apply the given range and -fetch the resources demanded by the client. To send the response, one can leverage the-`returnPage` to lift a collection of resources into a Servant Handler:+`PageHeaders` is a type alias provided by the library to declare the necessary response headers+we mentionned in introduction. Expanding the alias boils down to the following:  ```hs-defaultRange :: Range "name" String-defaultRange =-  getDefaultRange (Proxy @Color)+-- type MyHeaders+--   =  Header "Total-Count"   Int+--   :> Header "Accept-Ranges" (AcceptRanges '["name"])+--   :> Header "Content-Range" (ContentRange '["name"] Color)+--   :> Header "Next-Range"    (Ranges '["name"] Color)+``` -server :: Maybe (Ranges '["name"] Color) -> Handler (Headers (PageHeaders '["name"] Color) [Color])-server mrange = do-  let range =-        fromMaybe defaultRange (mrange >>= extractRange)+As a result, we will need to provide all those headers with the response in our handler. Worry+not, _servant-pagination_ provides an easy way to lift a collection of resources into such handler. -  returnRange range (applyRange range colors)+#### Server++Time to connect the last bits by defining the server implementation of our colorful API. The `Ranges`+type we've defined above (tight to the `Range` HTTP header) indicates the server to parse any `Range`+header, looking for the format defined in introduction with fields and target types we have just declared.+If no such header is provided, we will end up receiving `Nothing`. Otherwise, it will be possible+to _extract_ a `Range` from our `Ranges`.++```hs+server :: Server API+server = handler+  where+    handler :: Maybe (Ranges '["name"] Color) -> Handler (Headers MyHeaders [Color])+    handler mrange = do+      let range =+            fromMaybe defaultRange (mrange >>= extractRange)++      addHeader (length colors) <$> returnRange range (applyRange range colors)++main :: IO ()+main =+  Warp.run 1442 $ Servant.serve (Proxy @API) server ``` +Let's try it out using different ranges to observe the server's behavior. As a reminder, here's+the format we defined, where `<field>` here can only be `name` and `<value>` must parse to a `String`:++- `Range: <field> [<value>][; offset <o>][; limit <l>][; order <asc|desc>]`++Beside the target field, everything is pretty much optional in the `Range` HTTP header. Missing parts+are deducted from the `RangeOptions` that are part of the `HasPagination` instance. Therefore, all+following examples are valid requests to send to our server:++- 1 - `curl http://localhost:1442/colors -vH 'Range: name'`+- 2 - `curl http://localhost:1442/colors -vH 'Range: name; limit 2'`+- 3 - `curl http://localhost:1442/colors -vH 'Range: name Green; order asc; offset 1'`++Considering the following default options:++- `defaultRangeLimit: 100`+- `defaultRangeOffset: 0`+- `defaultRangeOrder: RangeDesc`++The previous ranges reads as follows:++- 1 - The first 100 colors, ordered by descending names+- 2 - The first 2 colors, ordered by descending names+- 3 - The 100 colors after `Green` (not included), ordered by ascending names.+ > See `examples/Simple.hs` for a running version of this guide. +## Going Forward -## Multiple Ranges+####  Multiple Ranges -As you've probably noticed, the 'Ranges' type takes a list of 'Symbol' of accepted fields. For-each of those 'Symbol', there must be a instance of `HasPagination` tighting the 'Symbol' to a-'Resource' and a given type. This enables you to define as many ranges as you want on a given-resource type. For instance, one could go for:+Note that in the simple above scenario, there's no ambiguity with `extractRange` and `returnRange`+because there's only one possible `Range` defined on our resource. Yet, as you've most probably+noticed, the `Ranges` combinator accepts a list of fields, each of which must declare a `HasPagination`+instance. Doing so will make the other helper functions more ambiguous and type annotation are+highly likely to be needed. + ```hs instance HasPagination Color "hex" where   type RangeType Color "hex" = String@@ -102,24 +225,23 @@ > See `examples/Complex.hs` for more complex examples.  -## Parsing Options+#### Parsing Options -By default, `servant-pagination` provides an implementation of `getRangeOptions` for each +By default, `servant-pagination` provides an implementation of `getRangeOptions` for each `HasPagination` type-class. However, this can be overwritten when defining a instance of that class to provide your own options. This options come into play when a `Range` header is-received and isn't fully specified (`limit`, `offset`, `order` are all optional) to provide +received and isn't fully specified (`limit`, `offset`, `order` are all optional) to provide default fallback values for those. -For instance, let's say we wanted to change the default limit to `5` in for our range on-`"name"`, we could tweak the corresponding `HasPagination` instance as follows:+For instance, let's say we wanted to change the default limit to `5` in a new range on+`"rgb"`, we could tweak the corresponding `HasPagination` instance as follows:  ```hs-instance HasPagination Color "name" where-  type RangeType Color "name" = String-  getFieldValue _ = name+instance HasPagination Color "rgb" where+  type RangeType Color "rgb" = String+  getFieldValue _ = sum . rgb   getRangeOptions _ _ = defaultOptions { defaultRangeLimit = 5 } ```-  ## Changelog 
servant-pagination.cabal view
@@ -5,7 +5,7 @@                      to communicate about a possible pagination feature of an endpoint, enabling a client to                      consume the API in different fashions (pagination with offset / limit, endless scroll using last                      referenced resources, ascending and descending ordering, etc.)-version:             2.1.0+version:             2.1.1 homepage:            https://github.com/chordify/haskell-servant-pagination bug-reports:         https://github.com/chordify/haskell-servant-pagination/issues license:             LGPL-3@@ -59,8 +59,8 @@    build-depends:       base >= 4 && < 5                      , text >= 1.2 && < 2-                     , servant >= 0.11 && <= 0.13-                     , servant-server >= 0.11 && <= 0.13+                     , servant >= 0.11 && < 0.15+                     , servant-server >= 0.11 && < 0.15                      , safe >= 0.3 && < 1    exposed-modules:     Servant.Pagination@@ -98,9 +98,9 @@    build-depends:       base >= 4 && < 5                      , aeson >= 1.2 && < 2-                     , servant >= 0.11 && <= 0.13+                     , servant >= 0.11 && < 0.15                      , servant-pagination-                     , servant-server >= 0.11 && <= 0.13+                     , servant-server >= 0.11 && < 0.15                      , warp >= 3.2 && < 4    other-modules:       Color@@ -138,9 +138,9 @@    build-depends:       base >= 4 && < 5                      , aeson >= 1.2 && < 2-                     , servant >= 0.11 && <= 0.13+                     , servant >= 0.11 && < 0.15                      , servant-pagination-                     , servant-server >= 0.11 && <= 0.13+                     , servant-server >= 0.11 && < 0.15                      , warp >= 3.2 && < 4    other-modules:       Color