defmodule JSONAPIPlug.Pagination do
@moduledoc """
JSON:API Pagination strategy
https://jsonapi.org/format/#fetching-pagination
Pagination links can be generated by implementing a module conforming to the
`JSONAPIPlug.Pagination` behavior and configuring the `pagination` of your API module:
```elixir
defmodule MyApp.MyController do
plug JSONAPIPlug.Plug, api: MyApp.MyApi, resource: MyApp.MyResource
end
```
```elixir
config :my_app, MyApp.API, pagination: MyApp.MyPagination
```
Actual pagination needs to be handled by your application and is outside the scope of this library.
Links can be generated using the `JSONAPIPlug.page` information stored in the connection `jsonapi_plug` private field
and by passing additional information to your pagination module by passing `options` from your controller.
See the tests for an example implementation of page based pagination strategy.
"""
alias JSONAPIPlug.{Document.LinkObject, Resource}
alias Plug.Conn
@type t :: module()
@type link :: :first | :last | :next | :prev
@type links :: %{link() => String.t()}
@type options :: Keyword.t()
@type params :: %{String.t() => String.t()}
@callback paginate(
Resource.t(),
[Resource.resource()],
Conn.t() | nil,
params(),
Resource.options()
) :: links()
@spec url_for(
Resource.t(),
[Resource.resource()],
Conn.t() | nil,
params() | nil
) ::
LinkObject.t()
def url_for(
resource,
data,
%Conn{query_params: query_params} = conn,
nil = _params
) do
query =
query_params
|> to_list_of_query_string_components()
|> URI.encode_query()
prepare_url(resource, data, conn, query)
end
def url_for(
resource,
data,
%Conn{query_params: query_params} = conn,
params
) do
url_for(
resource,
data,
%Conn{conn | query_params: Map.put(query_params, "page", params)},
nil
)
end
defp prepare_url(resource, data, conn, "" = _query),
do: Resource.url_for(resource, data, conn)
defp prepare_url(resource, data, conn, query) do
resource
|> Resource.url_for(data, conn)
|> URI.parse()
|> struct(query: query)
|> URI.to_string()
end
defp to_list_of_query_string_components(map) when is_map(map),
do: Enum.flat_map(map, &do_to_list_of_query_string_components/1)
defp do_to_list_of_query_string_components({key, value}) when is_list(value),
do: to_list_of_two_elem_tuple(key, value)
defp do_to_list_of_query_string_components({key, value}) when is_map(value),
do: Enum.flat_map(value, fn {k, v} -> to_list_of_two_elem_tuple("#{key}[#{k}]", v) end)
defp do_to_list_of_query_string_components({key, value}),
do: to_list_of_two_elem_tuple(key, value)
defp to_list_of_two_elem_tuple(key, value) when is_list(value),
do: Enum.map(value, &{"#{key}[]", &1})
defp to_list_of_two_elem_tuple(key, value), do: [{key, value}]
end