lib/malomo/request.ex

defmodule Malomo.Request do
  alias Malomo.{ Config, Helpers, Operation, Response }

  @type t ::
          %__MODULE__{
            body: String.t(),
            headers: Malomo.http_headers_t(),
            method: Malomo.http_method_t(),
            private: map,
            url: String.t()
          }

  defstruct [
    body: nil,
    headers: [],
    method: nil,
    private: %{},
    url: nil
  ]

  @spec new(Operation.t(), Config.t()) :: t
  def new(operation, config) do
    body = encode_body!(operation, config)

    headers = []
    headers = headers ++ [{ "accept", "application/vnd.malomo+json; version=2" }]
    headers = headers ++ [{ "authorization", "Bearer #{config.access_token}" }]
    headers = headers ++ Helpers.Headers.content_type(operation)
    headers = headers ++ config.http_headers

    url = Helpers.Url.to_string(operation, config)

    %__MODULE__{}
    |> Map.put(:body, body)
    |> Map.put(:headers, headers)
    |> Map.put(:method, operation.method)
    |> Map.put(:url, url)
  end

  defp encode_body!(%_{ method: method }, _config)
      when method == :delete or method == :get do
    ""
  end

  defp encode_body!(%_{ encoding: :www_form_urlencoded } = operation, _config) do
    operation.params
    |> Enum.into(%{})
    |> URI.encode_query()
  end

  defp encode_body!(operation, config) do
    operation.params
    |> Enum.into(%{})
    |> config.json_codec.encode!()
  end

  @spec send(t, Config.t()) :: Malomo.http_response_t()
  def send(request, config) do
    attempt = Map.get(request.private, :attempt, 0)

    attempt = attempt + 1

    private = Map.put(request.private, :attempt, attempt)

    request = Map.put(request, :private, private)

    request
    |> config.http_client.send(config.http_client_opts)
    |> retry(request, config)
    |> finish(config)
  end

  defp retry(response, _request, %_{ retry: retry }) when is_nil(retry) or retry == false do
    response
  end

  defp retry({ :ok, %{ status_code: status_code } } = response, request, config) when status_code >= 500 do
    do_retry(response, request, config)
  end

  defp retry({ :error, _ } = response, request, config) do
    do_retry(response, request, config)
  end

  defp retry(response, _request, _config) do
    response
  end

  defp do_retry(response, request, config) do
    attempt = Map.get(request.private, :attempt)

    max_attempts = Keyword.get(config.retry_opts, :max_attempts, 3)

    if max_attempts > attempt do
      seconds_to_wait = config.retry.wait_for(request, config)

      :timer.sleep(seconds_to_wait)

      request
      |> config.http_client.send(config.http_client_opts)
      |> retry(request, config)
    else
      response
    end
  end

  defp finish(response, config) do
    case response do
      { :ok, %{ status_code: status_code } = response } when status_code >= 400 ->
        { :error, Response.new(response, config) }
      { :ok, %{ status_code: status_code } = response } when status_code >= 200 ->
        { :ok, Response.new(response, config) }
      otherwise ->
        otherwise
    end
  end
end