lib/shopify/graphql/limiter.ex

defmodule Shopify.GraphQL.Limiter do
  use DynamicSupervisor

  alias Shopify.GraphQL.{ Config, Limiter, Request }

  @type name_t ::
          atom | { :global, any } | { :via, module, any }

  #
  # client
  #

  @spec send(Limiter.name_t(), Request.t(), Config.t()) :: Shopify.GraphQL.http_response_t()
  def send(limiter, request, config) do
    ensure_gen_stage_loaded!()

    partition = Limiter.Partition.name(limiter, config)

    start_partition(limiter, config)

    Limiter.Producer.send(partition, request, config)
  end

  @spec start_link(Keyword.t()) :: Supervisor.on_start()
  def start_link(opts) do
    ensure_gen_stage_loaded!()

    name = Keyword.get(opts, :name, __MODULE__)

    DynamicSupervisor.start_link(__MODULE__, :ok, name: name)
  end

  @spec start_partition(Limiter.name_t(), Config.t()) :: { :ok, pid } | { :error, term }
  def start_partition(limiter, config) do
    partition = Limiter.Partition.name(limiter, config)

    opts = Keyword.new()
    opts = Keyword.put(opts, :limiter_opts, config.limiter_opts)
    opts = Keyword.put(opts, :name, partition)

    spec = { Limiter.Partition, opts }

    case DynamicSupervisor.start_child(limiter, spec) do
      { :error, { :already_started, _pid } } ->
        :ignore
      otherwise ->
        otherwise
    end
  end

  @doc """
  Returns `true` if requests are currently being throttled due to rate limiting.
  Otherwise, returns `false`.
  """
  @spec throttled?(Limiter.name_t(), Keyword.t()) :: boolean
  def throttled?(limiter, config) do
    config = Config.new(config)

    limiter
    |> Limiter.Partition.name(config)
    |> Limiter.Producer.waiting?()
  end

  defp ensure_gen_stage_loaded! do
    unless Code.ensure_loaded?(GenStage) do
      raise """
      You are trying to use Shopify.GraphQL.Limiter but GenStage is not loaded.
      Make sure you have defined gen_stage as a dependency.
      """
    end
  end

  #
  # callbacks
  #

  @impl true
  def init(:ok) do
    DynamicSupervisor.init(strategy: :one_for_one)
  end
end