defmodule Pagination do @moduledoc """ Pagination plug """ @behaviour Plug def init(opts) do %{ enabled_paginations: Keyword.get(opts, :enabled_paginations, :all), default_limit: Keyword.get(opts, :default_limit, 20), max_limit: Keyword.get(opts, :max_limit, 50), initial_offset: Keyword.get(opts, :initial_offset, 0), initial_cursor: Keyword.get(opts, :initial_cursor), default_include_total_count: Keyword.get(opts, :default_include_total_count, false) } end def call(%Plug.Conn{params: params} = conn, config) do %{conn | params: Map.merge(params, %{"pagination" => get_pagination(params, config)})} end defp get_pagination(params, config) do case config.enabled_paginations do :offset -> get_offset_pagination(params, config) :cursor -> get_cursor_pagination(params, config) _ -> if Map.has_key?(params, "offset") do get_offset_pagination(params, config) else get_cursor_pagination(params, config) end end end defp get_offset_pagination(params, config) do %Pagination.Offset{ limit: get_limit(params, config), offset: get_offset(params, config), include_total_count: get_include_total_count(params, config) } end defp get_cursor_pagination(params, config) do %Pagination.Cursor{ limit: get_limit(params, config), cursor: get_cursor(params, config), include_total_count: get_include_total_count(params, config) } end defp get_limit(params, config) do limit = parse_param_to_integer(params["limit"], config.default_limit) if limit > 0, do: min(limit, config.max_limit), else: config.default_limit end defp get_offset(params, config) do page = parse_param_to_integer(params["offset"], config.initial_offset) max(page, config.initial_offset) end defp get_cursor(params, config) do parse_param_to_integer(params["cursor"], config.initial_cursor) end defp get_include_total_count(%{"include" => includes}, config) when is_list(includes), do: "total_count" in includes || config.default_include_total_count defp get_include_total_count(_params, config), do: config.default_include_total_count defp parse_param_to_integer(param, default_value \\ nil) defp parse_param_to_integer(nil, default_value), do: default_value defp parse_param_to_integer(param, _default_value) when is_integer(param), do: param defp parse_param_to_integer(param, default_value) do case Integer.parse(param) do {integer, _remainder_of_binary} -> integer :error -> default_value end end end