lib/http_client.ex

defmodule ExDeltaExchange.HTTPClient do
  alias ExDeltaExchange.JsonResponse

  @type verb :: :get | :post | :put | :delete
  @type path :: String.t()
  @type query_params :: map
  @type request_body :: String.t()
  @type credentials :: ExDeltaExchange.Credentials.t()
  @type error_reason :: Maptu.Extension.non_strict_error_reason() | HTTPoison.Error.t()
  @type response :: {:ok, JsonResponse.t()} | {:error, error_reason}

  @spec domain :: String.t()
  def domain, do: Application.get_env(:ex_delta_exchange, :domain, "api.delta.exchange")

  @spec protocol :: String.t()
  def protocol, do: Application.get_env(:ex_delta_exchange, :protocol, "https://")

  @spec origin :: String.t()
  def origin, do: protocol() <> domain()

  @spec url(path) :: String.t()
  def url(path), do: origin() <> api_path() <> path

  @spec api_path :: String.t()
  def api_path, do: Application.get_env(:ex_delta_exchange, :api_path, "/v2")

  @spec get(path, query_params) :: response
  def get(path, query_params) do
    non_auth_request(:get, path, query_params, "")
  end

  @spec get(path, query_params, credentials) :: response
  def get(path, query_params, credentials) do
    auth_request(:get, path, query_params, "", credentials)
  end

  @spec auth_request(verb, path, query_params, request_body, credentials) :: response
  def auth_request(verb, path, query_params, request_body, credentials) do
    headers =
      verb
      |> auth_headers(path, query_params, request_body, credentials)
      |> put_content_type(:json)

    %HTTPoison.Request{
      method: verb,
      url: url(path),
      headers: headers,
      body: request_body
    }
    |> HTTPoison.request()
    |> parse_response()
  end

  @spec non_auth_request(verb, path, query_params, request_body) :: response
  def non_auth_request(verb, path, _query_params, request_body) do
    headers = [] |> put_content_type(:json)

    %HTTPoison.Request{
      method: verb,
      url: url(path),
      headers: headers,
      body: request_body
    }
    |> HTTPoison.request()
    |> parse_response()
  end

  defp put_content_type(headers, :json) do
    Keyword.put(headers, :"Content-Type", "application/json")
  end

  defp auth_headers(http_method, path, query_params, request_body, credentials) do
    normalized_http_method = http_method |> normalize_http_method
    timestamp = ExDeltaExchange.Auth.timestamp()
    query_string = URI.encode_query(query_params)

    signature =
      ExDeltaExchange.Auth.sign(
        normalized_http_method,
        timestamp,
        api_path() <> path,
        query_string,
        request_body,
        credentials.api_secret
      )

    ["api-key": credentials.api_key, signature: signature, timestamp: timestamp]
  end

  defp normalize_http_method(:get), do: "GET"
  defp normalize_http_method(:post), do: "POST"
  defp normalize_http_method(:put), do: "PUT"
  defp normalize_http_method(:delete), do: "DELETE"

  defp parse_response({:ok, %HTTPoison.Response{body: body}}) do
    {:ok, json} = Jason.decode(body)
    Mapail.map_to_struct(json, JsonResponse, transformations: [:snake_case])
  end

  defp parse_response({:error, _} = result) do
    result
  end
end