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 +5/−0
- README.md +162/−40
- servant-pagination.cabal +7/−7
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