servant-pagination: Type-safe pagination for Servant APIs

[ lgpl, library, web ] [ Propose Tags ]

This module offers opinionated helpers to declare a type-safe and a flexible pagination mecanism for Servant APIs. This design, inspired by Heroku's API, provides a small framework 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.)


[Skip to Readme]

Modules

[Index]

Flags

Manual Flags

NameDescriptionDefault
examples

build examples executables

Disabled

Use -f <flag> to enable a flag, or -f -<flag> to disable that flag. More info

Downloads

Note: This package has metadata revisions in the cabal description newer than included in the tarball. To unpack the package including the revisions, use 'cabal get'.

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees

Candidates

  • No Candidates
Versions [RSS] 1.0.0, 2.0.0, 2.1.0, 2.1.1, 2.1.2, 2.1.3, 2.2.0, 2.2.1, 2.2.2, 2.3.0, 2.4.0, 2.4.1, 2.4.2, 2.5.0, 2.5.1 (info)
Change log CHANGELOG.md
Dependencies aeson (>=1.2 && <2), base (>=4 && <5), safe (>=0.3 && <1), servant (>=0.11 && <0.14), servant-pagination, servant-server (>=0.11 && <0.14), text (>=1.2 && <2), warp (>=3.2 && <4) [details]
License LGPL-3.0-only
Copyright (c) 2018 Chordify
Author Chordify
Maintainer Chordify <haskelldevelopers@chordify.net>, Matthias Benkort <matthias.benkort@gmail.com>
Revised Revision 1 made by KtorZ at 2018-04-17T07:06:37Z
Category Web
Home page https://github.com/chordify/haskell-servant-pagination
Bug tracker https://github.com/chordify/haskell-servant-pagination/issues
Source repo head: git clone git://github.com/chordify/haskell-servant-pagination.git
Uploaded by KtorZ at 2018-04-16T13:29:08Z
Distributions NixOS:2.5.1
Reverse Dependencies 1 direct, 0 indirect [details]
Executables servant-pagination-complex, servant-pagination-simple
Downloads 5944 total (37 in the last 30 days)
Rating (no votes yet) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Docs available [build log]
Last success reported on 2018-04-16 [all 1 reports]

Readme for servant-pagination-2.1.0

[back to package description]

servant-pagination

Overview

This module offers opinionated helpers to declare a type-safe and a flexible pagination mechanism for Servant APIs. This design, inspired by Heroku's API, provides a small framework 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.)

Therefore, client may provide a Range header with their request with the following format:

  • Range: <field> [<value>][; offset <o>][; limit <l>][; order <asc|desc>]

For example: Range: createdAt 2017-01-15T23:14:67.000Z; offset 5; order desc indicates that the client is willing to retrieve the next batch of document in descending order that were 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:

  • Accept-Ranges: A comma-separated list of field 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

For example:

  • Accept-Ranges: createdAt, modifiedAt
  • Content-Range: createdAt 2017-01-15T23:14:51.000Z..2017-02-18T06:10:23.000Z
  • Next-Range: createdAt 2017-02-19T12:56:28.000Z; offset 0; limit 100; order desc

Getting Starting

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.

data Color = Color
  { name :: String
  , rgb  :: [Int]
  , hex  :: String
  } deriving (Eq, Show, Generic)

instance ToJSON Color where
  toJSON = genericToJSON defaultOptions

instance HasPagination Color "name" where
  type RangeType Color "name" = String
  getFieldValue _ = name

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:

type API =
  "colors"
  :> Header "Range" (Ranges '["name"] Color)
  :> GetPartialContent '[JSON] (Headers (PageHeaders '["name"] Color) [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:

defaultRange :: Range "name" String
defaultRange =
  getDefaultRange (Proxy @Color)

server :: Maybe (Ranges '["name"] Color) -> Handler (Headers (PageHeaders '["name"] Color) [Color])
server mrange = do
  let range =
        fromMaybe defaultRange (mrange >>= extractRange)

  returnRange range (applyRange range colors)

See examples/Simple.hs for a running version of this guide.

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:

instance HasPagination Color "hex" where
  type RangeType Color "hex" = String
  getFieldValue _ = hex

-- to then define: Ranges '["name", "hex"] Color

See examples/Complex.hs for more complex examples.

Parsing Options

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 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:

instance HasPagination Color "name" where
  type RangeType Color "name" = String
  getFieldValue _ = name
  getRangeOptions _ _ = defaultOptions { defaultRangeLimit = 5 }

Changelog

CHANGELOG.md

License

LGPL-3 © 2018 Chordify