defmodule Virgil.Circuit do
@moduledoc """
Circuit implementation
"""
@type t :: %__MODULE__{
name: atom(),
error_threshold: integer(),
reset_timeout: integer(),
failures: integer(),
state: :open | :closed | :half_open
}
defstruct name: nil,
error_threshold: 0,
reset_timeout: 0,
failures: 0,
state: nil
@callback run(any()) :: {:ok, any()} | {:error, any()}
defmacro __using__(opts) do
quote do
@behaviour Virgil.Circuit
opts = unquote(opts)
require Logger
alias Virgil.{
Config,
Handler
}
alias __MODULE__, as: Circuit
@error_threshold opts[:error_threshold] || Config.default_error_threshold()
@reset_timeout opts[:reset_timeout] || Config.default_reset_timeout()
@spec execute(any()) :: {:ok, any()} | {:error, any()}
def execute(params) do
{:ok, is_closed?} =
circuit()
|> circuit_manager().adapter().is_closed?()
if is_closed? do
Logger.info("[#{__MODULE__}] Running circuit")
params
|> run()
|> handle_response()
else
Logger.info("[#{__MODULE__}] Circuit is not closed")
end
end
@spec error_threshold :: integer()
def error_threshold, do: @error_threshold
@spec reset_timeout :: integer()
def reset_timeout, do: @reset_timeout
def circuit do
%Virgil.Circuit{
name: __MODULE__,
failures: 0,
state: :closed,
error_threshold: @error_threshold,
reset_timeout: @reset_timeout
}
end
defp circuit_manager, do: Config.circuit_manager()
defp handle_response({:ok, response}) do
:telemetry.execute(
[:virgil, :circuit, :success],
%{circuit_response: response},
%{
circuit: __MODULE__
}
)
end
defp handle_response({:error, response}) do
:telemetry.execute(
[:virgil, :circuit, :failure],
%{circuit_response: response},
%{
circuit: __MODULE__
}
)
end
end
end
end