lib/retry.ex

defmodule Moar.Retry do
  # @related [test](/test/retry_test.exs)

  # TODO:
  # - implement: retry_until(DateTime.t(), fun()) :: {:ok, any()} | {:error, :timeout}
  # - implement: retry_for(pos_integer(), fun()) :: {:ok, any()} | {:error, :timeout}
  #
  # see: https://github.com/RatioPBC/euclid/blob/main/test/support/helpers/retry.ex

  @spec rescue_for!(pos_integer(), (() -> any())) :: any() | no_return
  def rescue_for!(timeout_ms \\ 5000, fun) when is_function(fun) and is_number(timeout_ms) do
    DateTime.utc_now()
    |> DateTime.add(timeout_ms, :millisecond)
    |> rescue_until!(fun)
  end

  @spec rescue_until!(DateTime.t(), (() -> any())) :: any() | no_return
  def rescue_until!(%DateTime{} = time, fun) when is_function(fun) do
    fun.()
  rescue
    e ->
      if DateTime.compare(time, DateTime.utc_now()) == :gt do
        :timer.sleep(100)
        rescue_until!(time, fun)
      else
        reraise e, __STACKTRACE__
      end
  end
end