Skip to content

Cookbook: Autopaginate using "Link" header

Tymon Tobolski edited this page Mar 26, 2018 · 1 revision
defmodule AutoPaginate do
  # requires tesla 0.x

  def call(env, run, []) do
    env = run.(env)

    if env.status == 200 && Map.has_key?(env, :rels) do
      next_page = page(env.rels["next"])
      last_page = page(env.rels["last"])

      cond do
        next_page == 2 && last_page ->
          body = fetch_parallel(env, next_page, last_page)
          %{env | body: (env.body ++ body)}

        !last_page && next_page ->
          body = fetch_next(env).body
          %{env | body: (env.body ++ body)}

        true ->
          env
      end
    else
      env
    end
  end

  defp fetch_parallel(env, next_page, last_page) do
    (next_page..last_page)
    |> Gitalyzer.Utils.parmap(fn e -> fetch_page(env, e) end)
    |> Enum.reduce([], fn(e,a) -> a ++ e.body end)
  end

  defp page(rel) do
    if rel do
      Regex.run(~r/\A.+page=(\d+)\z/, rel, capture: :all_but_first)
      |> List.first
      |> String.to_integer
    else
      nil
    end
  end

  defp with_page(url, page) do
    url |> String.replace("2", to_string(page))
  end

  defp fetch_page(env, page) do
    env._client |> env._module.get(with_page(env.rels["next"], page))
  end

  defp fetch_next(env) do
    env._client |> env._module.get(env.rels["next"])
  end
end
defmodule MyApi do
  use Tesla

  plug Tesla.Middleware.DecodeRels
  plug Autopaginate
end