Current section
Files
Jump to
Current section
Files
lib/scrivener/headers.ex
defmodule Scrivener.Headers do
@moduledoc """
Helpers for paginating API responses with [Scrivener](https://github.com/drewolson/scrivener) and HTTP headers. Implements [RFC-5988](https://mnot.github.io/I-D/rfc5988bis/), the proposed standard for Web linking.
Use `paginate/2` to set the pagination headers:
def index(conn, params) do
page = MyApp.Person
|> where([p], p.age > 30)
|> order_by([p], desc: p.age)
|> preload(:friends)
|> MyApp.Repo.paginate(params)
conn
|> Scrivener.Headers.paginate(page)
|> render("index.json", people: page.entries)
end
"""
import Plug.Conn
alias Plug.Conn.Query
@rels ~w(first last next prev)
@doc """
Add HTTP headers for a `Scrivener.Page`.
"""
@spec paginate(Plug.Conn.t, Scrivener.Page.t) :: Plug.Conn.t
def paginate(conn, page) do
conn
|> put_link_header(page)
|> put_resp_header("Total", Integer.to_string(page.total_entries))
|> put_resp_header("Per-Page", Integer.to_string(page.page_size))
end
defp put_link_header(conn, page) do
link = conn
|> pages(page)
|> Enum.join(", ")
put_resp_header(conn, "Link", link)
end
defp pages(conn, page) do
@rels
|> Enum.map(&(page_link(conn, page, &1)))
|> Enum.reject(&(&1 == ""))
end
defp page_link(conn, %{}, "first"), do: page_link(conn, 1, "first")
defp page_link(conn, %{total_pages: total}, "last"), do: page_link(conn, total, "last")
defp page_link(_conn, %{page_number: page, total_pages: total}, "next") when page == total, do: ""
defp page_link(conn, %{page_number: page}, "next"), do: page_link(conn, page + 1, "next")
defp page_link(_conn, %{page_number: 1}, "prev"), do: ""
defp page_link(conn, %{page_number: page}, "prev"), do: page_link(conn, page - 1, "prev")
defp page_link(conn, page, rel) when is_integer(page) do
conn
|> paged_url(page)
|> page_link(rel)
end
defp page_link(url, rel), do: "<#{url}>; rel=\"#{rel}\""
defp paged_url(conn, page) do
conn.query_string
|> Query.decode
|> Map.put("page", page)
|> Query.encode
|> url(conn)
end
defp url(query, conn) do
scheme = Atom.to_string(conn.scheme)
scheme = "#{scheme}://"
unless "" == query, do: query = "?#{query}"
port = if conn.port in [80, 443], do: "", else: ":#{conn.port}"
[scheme, conn.host, port, conn.request_path, query]
|> Enum.join("")
end
end