Current section
Files
Jump to
Current section
Files
lib/ex_bitmex/rest/request.ex
defmodule ExBitmex.Rest.Request do
@type path :: String.t()
@type credentials :: ExBitmex.Credentials.t()
@type params :: map
@type rate_limit :: ExBitmex.RateLimit.t()
@type message :: String.t()
@type bad_request :: {:bad_request, error :: map}
@type forbidden :: {:forbidden, message}
@type unauthorized :: {:unauthorized, message}
@type service_unavailable :: {:service_unavailable, message}
@type nonce_not_increasing :: {:nonce_not_increasing, message}
@type auth_error_reason ::
:timeout
| :connect_timeout
| :non_existent_domain
| :not_found
| bad_request
| :rate_limited
| :overloaded
| :bad_gateway
| service_unavailable
| nonce_not_increasing
| :ip_forbidden
| forbidden
| :invalid_signature
| unauthorized
@type auth_error_response :: {:error, auth_error_reason, rate_limit | nil}
@type auth_success_response :: {:ok, map | [map], rate_limit}
@type auth_response :: auth_success_response | auth_error_response
@type non_auth_error_reason ::
:timeout
| :not_found
| bad_request
| :overloaded
| service_unavailable
@type non_auth_response ::
{:ok, map | [map], rate_limit} | {:error, non_auth_error_reason, rate_limit | nil}
def send(request) do
request
|> HTTPoison.request()
|> parse_rate_limits
|> parse_response
end
@limit_header "X-RateLimit-Limit"
@remaining_header "X-RateLimit-Remaining"
@reset_header "X-RateLimit-Reset"
defp parse_rate_limits({result, %HTTPoison.Response{headers: headers} = response}) do
limit_headers =
headers
|> Enum.reduce(
%{},
fn {k, v}, acc ->
case k do
@limit_header -> Map.put(acc, :limit, v)
@remaining_header -> Map.put(acc, :remaining, v)
@reset_header -> Map.put(acc, :reset, v)
_ -> acc
end
end
)
rate_limit = limit_headers |> build_rate_limit
{result, response, rate_limit}
end
defp parse_rate_limits({result, %HTTPoison.Error{} = error}) do
{result, error, nil}
end
defp build_rate_limit(%{limit: limit, remaining: remaining, reset: reset}) do
%ExBitmex.RateLimit{
limit: limit |> String.to_integer(),
remaining: remaining |> String.to_integer(),
reset: reset |> String.to_integer()
}
end
defp build_rate_limit(%{}), do: nil
defp parse_response({:ok, %HTTPoison.Response{status_code: 200, body: body}, rate_limit}) do
json = body |> Jason.decode!()
{:ok, json, rate_limit}
end
defp parse_response({:ok, %HTTPoison.Response{status_code: 400, body: body}, rate_limit}) do
json = Jason.decode!(body)
message =
json
|> Map.fetch!("error")
|> Map.fetch!("message")
reason =
if message =~ "Nonce is not increasing" do
{:nonce_not_increasing, message}
else
{:bad_request, json}
end
{:error, reason, rate_limit}
end
defp parse_response({:ok, %HTTPoison.Response{status_code: 401, body: body}, rate_limit}) do
message =
body
|> Jason.decode!()
|> Map.fetch!("error")
|> Map.fetch!("message")
reason =
if message == "Signature not valid." do
:invalid_signature
else
{:unauthorized, message}
end
{:error, reason, rate_limit}
end
defp parse_response({:ok, %HTTPoison.Response{status_code: 403, body: body}, rate_limit}) do
message =
body
|> Jason.decode!()
|> Map.fetch!("error")
|> Map.fetch!("message")
reason =
if message == "This IP address is not allowed to use this key." do
:ip_forbidden
else
{:forbidden, message}
end
{:error, reason, rate_limit}
end
defp parse_response({:ok, %HTTPoison.Response{status_code: 404}, rate_limit}) do
{:error, :not_found, rate_limit}
end
defp parse_response({:ok, %HTTPoison.Response{status_code: 429}, rate_limit}) do
{:error, :rate_limited, rate_limit}
end
defp parse_response({:ok, %HTTPoison.Response{status_code: 502}, rate_limit}) do
{:error, :bad_gateway, rate_limit}
end
defp parse_response({
:ok,
%HTTPoison.Response{status_code: 503, body: body},
rate_limit
}) do
message =
body
|> Jason.decode!()
|> Map.fetch!("error")
|> Map.fetch!("message")
reason =
if message =~ "The system is currently overloaded" do
:overloaded
else
{:service_unavailable, message}
end
{:error, reason, rate_limit}
end
defp parse_response({:error, %HTTPoison.Error{reason: :timeout}, nil}) do
{:error, :timeout, nil}
end
defp parse_response({:error, %HTTPoison.Error{reason: :connect_timeout}, nil}) do
{:error, :connect_timeout, nil}
end
@non_existent_domain [:nxdomain, "nxdomain"]
defp parse_response({:error, %HTTPoison.Error{reason: reason}, nil})
when reason in @non_existent_domain do
{:error, :non_existent_domain, nil}
end
end