defmodule Throttex do @moduledoc """ Throttex is a GenServer which provides interface for rate limit control. Basically it provides two main functions: *request* and *release*. Performing _request_, it allows or disallows operation accordingly to the *Rate Limit* rules. When the _request_ has been completed, _release_ is expected to be called to confirm operation. """ use GenServer require Logger alias Throttex.Types alias Throttex.Core defmacro __using__(_opts \\ []) do quote location: :keep do defdelegate start(args), to: Throttex defdelegate start_link(args), to: Throttex defdelegate child_spec(args), to: Throttex end end def start(args) do args = validate_args(args) GenServer.start(__MODULE__, args, name: args[:name]) end def start_link(args) do args = validate_args(args) GenServer.start_link(__MODULE__, args, name: args[:name]) end @impl true def init(args) do {:ok, Core.initial_state(args[:rates])} end @spec request(GenServer.server(), rate_id :: term()) :: {:ok, request_id :: term()} | {:error, :invalid_rate | :unavailable} def request(pid \\ __MODULE__, rate_id) do GenServer.call(pid, {:request, rate_id}) end @spec release(GenServer.server(), Types.timestamp()) :: :ok def release(pid \\ __MODULE__, request_id) do GenServer.cast(pid, {:release, request_id}) end @impl true def handle_call({:request, rate_id}, _from, state) do case Core.request(state, rate_id, timestamp()) do {:ok, request_id, new_state} -> {:reply, {:ok, request_id}, new_state} {:error, reason} -> {:reply, {:error, reason}, state} end end @impl true def handle_call(message, _from, state) do Logger.warning("Got unexpected message in handle_call/3: #{inspect(message)}") {:reply, {:error, :unsupported_method}, state} end @impl true def handle_cast({:release, request_id}, state) do case Core.release(state, request_id, timestamp()) do {:ok, new_state} -> {:noreply, new_state} :error -> {:noreply, state} end end @impl true def handle_cast(message, state) do Logger.warning("Got unexpected message in handle_cast/2: #{inspect(message)}") {:noreply, state} end @impl true def handle_info(message, state) do Logger.warning("Got unexpected message in handle_info/2: #{inspect(message)}") {:noreply, state} end defp timestamp(), do: System.system_time(:millisecond) defp validate_args(args) do [ name: args[:name], rates: Keyword.fetch!(args, :rates) ] end end