defmodule Stellar.Horizon.Collection do @moduledoc """ Paginates data and returns collection-based results for multiple Horizon endpoints. """ alias Stellar.Horizon.CollectionError @type resource :: {module(), function()} @type records :: [resource(), ...] @type response :: map() @type pagination :: {function(), Keyword.t()} @type t :: %__MODULE__{records: records(), prev: function(), next: function()} defstruct [:records, :next, :prev] @spec new(response :: response(), resource :: resource()) :: t() def new( %{ _embedded: %{records: records}, _links: %{prev: %{href: prev_url}, next: %{href: next_url}} }, {resource, paginate_fun} ) do %__MODULE__{ records: Enum.map(records, &resource.new/1), prev: paginate({paginate_fun, prev_url}), next: paginate({paginate_fun, next_url}) } end def new(_response, _resource), do: raise(CollectionError, :invalid_collection) @spec paginate(pagination :: pagination()) :: function() defp paginate({paginate_fn, url}) do fn -> url |> pagination_query() |> paginate_fn.() end end @spec pagination_query(url :: String.t()) :: Keyword.t() defp pagination_query(url) do case URI.parse(url) do %URI{query: query} when not is_nil(query) -> query |> URI.query_decoder() |> Enum.map(fn {key, value} -> {String.to_atom(key), value} end) _uri -> [] end end end