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.)

Maintainer's Corner

Readme for servant-pagination-2.1.3

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 can provide a Range header with their request with the following format:

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

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

Getting Started

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:

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.

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:

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:

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.

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.

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:

Going Forward

Multiple Ranges

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.

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 a new range on
"rgb", we could tweak the corresponding HasPagination instance as follows: