Skip to content

Instantly share code, notes, and snippets.

@dzfranklin
Last active July 5, 2021 20:59
Show Gist options
  • Save dzfranklin/988f2a6af2a3a223c80d46566a8338da to your computer and use it in GitHub Desktop.
Save dzfranklin/988f2a6af2a3a223c80d46566a8338da to your computer and use it in GitHub Desktop.
defmodule Wrench.Hex.Api.Requester do
use GenServer
require Logger
@config Application.get_env(:wrench, __MODULE__)
@api_key @config[:api_key]
@ua "Wrench/#{Application.spec(:wrench, :vsn)} (contact #{Application.get_env(:wrench, :operator_contact)})"
@endpoint "https://hex.pm/api"
def start_link(opts) do
GenServer.start_link(__MODULE__, opts)
end
@doc """
## Opts
- make_request: Optional, mostly for testing.
Should have same behavior as private fun `__MODULE__.make_request/1`
"""
@impl true
def init(opts) do
{:ok,
%{
make_request: Keyword.get(opts, :make_request, &make_request/1),
limit: %{
remaining: 1,
reset_time: nil
}
}}
end
@impl true
def handle_call({:request, request}, _from, state) do
unix_now = DateTime.utc_now() |> DateTime.to_unix()
limit =
if state.limit.remaining <= 0 do
if state.limit.reset_time <= unix_now do
# Give us one request to find the new limits
%{state.limit | remaining: 1, reset_time: nil}
else
# TODO: How to wait until reset_time
end
else
state.limit
end
if limit.remaining > 0 do
with {:ok, reply, new_limit} <- make_request(request) do
{:reply, reply, %{state | limit: new_limit}}
end
end
end
defp make_request(
%{method: method, path: path, params: params, body: body, etag: etag} = request
) do
Logger.info("Requesting #{inspect(request)}")
headers = [{"Authorization", @api_key}, {"User-Agent", @ua}]
headers =
if is_nil(etag) do
headers
else
[{"ETag", etag} | headers]
end
reply =
Finch.build(
method,
"#{@endpoint}/#{path}?#{URI.encode_query(params, :rfc3986)}",
headers,
body
)
|> Finch.request(Wrench.Finch)
with {:ok, %Finch.Response{headers: headers} = reply} <- reply do
headers = Map.new(headers)
remaining = headers["x-ratelimit-remaining"]
reset = headers["x-ratelimit-reset"]
{:ok, {reply, %{remaining: remaining, reset_time: reset}}}
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment