lib/shopify/graphql/request.ex

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

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

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

  @spec new(Operation.t(), Config.t()) :: t
  def new(operation, config) do
    url = Helpers.Url.to_string(config)

    headers = []
    headers = headers ++ [{ "content-type", "application/json" }]
    headers = headers ++ [{ "x-shopify-access-token", config.access_token }]
    headers = headers ++ config.http_headers

    body = Map.new()
    body = Map.put(body, :query, operation.query)
    body = Map.put(body, :variables, operation.variables)
    body = config.json_codec.encode!(body)

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

  @spec send(t, Config.t()) :: Shopify.GraphQL.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