defmodule Mixpanel.HTTP do
@moduledoc """
Adapter specification for HTTP clients and API for accessing them.
"""
require Logger
@max_retries 3
@callback get(url :: String.t(), headers :: [{String.t(), binary}], opts :: keyword) ::
{:ok, status :: 200..599, headers :: [{String.t(), binary}], body :: term}
| {:error, String.t()}
@callback post(
url :: String.t(),
body :: binary,
headers :: [{String.t(), binary}],
opts :: keyword
) ::
{:ok, status :: 200..599, headers :: [{String.t(), binary}], body :: term}
| {:error, String.t()}
@spec get(
client :: module,
url :: String.t(),
headers :: [{String.t(), binary}],
opts :: keyword
) ::
{:ok, status :: 200..599, headers :: [{String.t(), binary}], body :: term}
| {:error, String.t()}
def get(client, url, headers, opts) do
{params, opts} = Keyword.pop(opts, :params, nil)
retry(url, fn -> client.get(build_url(url, params), headers, opts) end, @max_retries)
end
@spec post(
client :: module,
url :: String.t(),
payload :: binary,
headers :: [{String.t(), binary}],
opts :: keyword
) ::
{:ok, status :: 200..599, headers :: [{String.t(), binary}], body :: term}
| {:error, String.t()}
def post(client, url, payload, headers, opts) do
retry(url, fn -> client.post(url, payload, headers, opts) end, @max_retries)
end
@spec retry(String.t(), (-> {:ok, any, any, any} | {:error, String.t()}), non_neg_integer) ::
{:ok, any, any, any} | {:error, String.t()}
defp retry(url, fun, attempts_left) do
case fun.() do
{:ok, 200, _headers, "1"} = ok ->
ok
other ->
attempts_left = attempts_left - 1
reason =
case other do
{:ok, status, _headers, _body} ->
Logger.warning(%{
message: "Retrying request",
attempts_left: attempts_left,
url: url,
http_status: status
})
"HTTP #{status}"
{:error, reason} ->
Logger.warning(%{
message: "Won't retry to request due to a client error",
attempts_left: attempts_left,
url: url,
error: reason
})
reason
end
if attempts_left > 0 do
retry(url, fun, attempts_left)
else
{:error, reason}
end
end
end
defp build_url(url, nil), do: url
defp build_url(url, data: data), do: "#{url}?#{URI.encode_query(data: data)}"
end