defmodule HeliumElixir.BlockchainApi do
use HTTPoison.Base
alias HTTPoison.Response
alias HeliumElixir.BlockchainApi.BlockchainRequestConfig
alias HeliumElixir.BlockchainApi.TakeMinConfig
def post_json(
%BlockchainRequestConfig{headers: headers, base_url: base_url, path: path},
body \\ %{}
) do
case post(base_url <> path, headers, Jason.encode!(body)) do
{:ok, %Response{body: body}} ->
{:ok, body |> Jason.decode!()}
{:error, %HTTPoison.Error{reason: reason}} ->
{:error, reason}
end
end
def get_json(
%BlockchainRequestConfig{headers: headers, base_url: base_url, path: path},
params \\ %{}
) do
case get(base_url <> path, headers, params: params, follow_redirect: true) do
{:ok, %Response{body: body, status_code: status_code}} ->
case status_code do
200 ->
response = body |> Jason.decode!()
{:ok, response}
400 ->
IO.puts("Cursor invalid")
{:error, :cursor_invalid}
404 ->
IO.puts("404")
{:error, :four_oh_four}
503 ->
IO.puts("Too busy")
{:error, :too_busy}
_ ->
IO.puts("Unknown error status code: #{status_code}")
IO.inspect(body)
{:error, String.to_atom("unknown_#{status_code}")}
end
{:error, %HTTPoison.Error{reason: reason}} ->
{:error, reason}
end
end
@doc """
Pages the given endpoint until `min_count` is reached.
The data returned will be at least the amount requested unless no more items exist.
Returns `{:ok, %{"data" => data, "cursor" => cursor}}`.
"""
def take_min(
%BlockchainRequestConfig{} = config,
%TakeMinConfig{min_count: min_count, params: params, acc: acc, cursor: cursor}
) do
case get_json(config, Map.merge(params, %{cursor: cursor})) do
{:ok, %{"data" => data, "cursor" => next_cursor}} ->
next_data = acc ++ data
acc_count = length(next_data)
if(acc_count >= min_count || is_nil(next_cursor)) do
{:ok, %{"data" => next_data, "cursor" => next_cursor}}
else
take_min(config, %TakeMinConfig{
min_count: min_count,
params: params,
acc: next_data,
cursor: next_cursor
})
end
{:ok, %{"data" => data}} ->
{:ok, %{"data" => acc ++ data, "cursor" => ""}}
{:error, error} ->
len = length(acc)
case len do
0 ->
{:error, error}
_ ->
{:ok, %{"data" => acc, "cursor" => cursor}}
end
_ ->
{:error}
end
end
# paginates through all entries of a provided url
def paginate_all(%BlockchainRequestConfig{} = config, params \\ %{}, acc \\ [], cursor \\ nil) do
case get_json(config, Map.merge(params, %{cursor: cursor})) do
{:ok, %{"data" => data, "cursor" => next_cursor}} ->
paginate_all(config, params, acc ++ data, next_cursor)
{:ok, %{"data" => data}} ->
{:ok, acc ++ data}
{:error, error} ->
{:error, error}
end
end
def each_page(%BlockchainRequestConfig{} = config, fun, cursor \\ nil) do
case get_json(config, %{cursor: cursor}) do
{:ok, %{"data" => data, "cursor" => next_cursor}} ->
fun.(data)
each_page(config, fun, next_cursor)
{:ok, %{"data" => data}} ->
fun.(data)
:ok
{:error, error} ->
{:error, error}
end
end
# paginates through all entries of a provided url, while fun returns true
def paginate_while(
%BlockchainRequestConfig{} = config,
fun,
params \\ %{},
acc \\ [],
cursor \\ nil
) do
case get_json(config, Map.merge(params, %{cursor: cursor})) do
{:ok, %{"data" => data, "cursor" => next_cursor}} ->
filtered_data = data |> Enum.take_while(fn d -> fun.(d) end)
if length(filtered_data) == length(data) do
IO.puts("keep going")
paginate_while(config, fun, params, acc ++ filtered_data, next_cursor)
else
IO.puts("fun returned subset, we're done")
{:ok, acc ++ filtered_data}
end
{:ok, %{"data" => data}} ->
filtered_data = data |> Enum.take_while(fn d -> fun.(d) end)
{:ok, acc ++ filtered_data}
{:error, error} ->
{:error, error}
end
end
# returns the first entry that fun evaluates true for
def paginate_find(%BlockchainRequestConfig{} = config, fun, params \\ %{}, cursor \\ nil) do
case get_json(config, Map.merge(params, %{cursor: cursor})) do
{:ok, %{"data" => data, "cursor" => next_cursor}} ->
case Enum.find(data, fun) do
nil ->
paginate_find(config, fun, params, next_cursor)
found ->
{:ok, found}
end
{:ok, %{"data" => data}} ->
case Enum.find(data, fun) do
nil ->
{:error, :not_found}
found ->
{:ok, found}
end
{:error, error} ->
{:error, error}
end
end
end