Current section
Files
Jump to
Current section
Files
lib/hook_sniff_api/retry_middleware.ex
defmodule HookSniffAPI.RetryMiddleware do
@moduledoc """
Tesla middleware for automatic retry on 429 (rate limit) and 5xx errors.
Respects the Retry-After header for 429 responses.
"""
@behaviour Tesla.Middleware
@default_retry_schedule [50, 100, 200]
@impl true
def call(env, next, opts) do
retry_schedule = Keyword.get(opts, :retry_schedule, @default_retry_schedule)
do_request(env, next, retry_schedule, 0)
end
defp do_request(env, next, retry_schedule, attempt) do
case Tesla.run(env, next) do
{:ok, %Tesla.Env{status: 429} = response} ->
if attempt < length(retry_schedule) do
delay = get_retry_delay(response, retry_schedule, attempt)
Process.sleep(delay)
new_env = add_retry_header(env, attempt + 1)
do_request(new_env, next, retry_schedule, attempt + 1)
else
{:ok, response}
end
{:ok, %Tesla.Env{status: status} = response} when status >= 500 ->
if attempt < length(retry_schedule) do
delay = Enum.at(retry_schedule, attempt, 200)
Process.sleep(delay)
new_env = add_retry_header(env, attempt + 1)
do_request(new_env, next, retry_schedule, attempt + 1)
else
{:ok, response}
end
other ->
other
end
end
defp get_retry_delay(response, retry_schedule, attempt) do
case Tesla.get_header(response, "retry-after") do
nil ->
Enum.at(retry_schedule, attempt, 200)
retry_after ->
case Integer.parse(retry_after) do
{seconds, _} -> seconds * 1000
:error -> Enum.at(retry_schedule, attempt, 200)
end
end
end
defp add_retry_header(env, count) do
Tesla.put_header(env, "hooksniff-retry-count", to_string(count))
end
end