defmodule Reactor.Executor.Init do
@moduledoc """
Handle argument checking and state setup for a Reactor run.
"""
alias Reactor.Executor
import Reactor, only: :macros
import Reactor.Utils
@doc false
@spec init(Reactor.t(), Reactor.inputs(), Reactor.context(), Reactor.options()) ::
{:ok, Reactor.t(), state :: map} | {:error, any}
def init(reactor, _inputs, _context, _options) when not is_reactor(reactor),
do: {:error, ArgumentError.exception(message: "`reactor` is not a Reactor.")}
def init(reactor, inputs, context, options) do
with {:ok, inputs} <- into_map(inputs),
{:ok, inputs} <- validate_inputs(reactor, inputs),
{:ok, context} <- into_map(context),
{:ok, options} <- into_map(options) do
state = Executor.State.init(options)
context =
reactor.context
|> deep_merge(context)
|> deep_merge(%{private: %{inputs: inputs}})
{:ok, %{reactor | context: context}, state}
end
end
defp into_map(map) when is_map(map), do: {:ok, map}
defp into_map(mappish) do
{:ok, Map.new(mappish)}
rescue
_error in [Protocol.UndefinedError, ArgumentError] ->
{:error,
ArgumentError.exception(message: "`#{inspect(mappish)}` cannot be converted into a map.")}
end
defp validate_inputs(reactor, inputs) do
valid_input_names = MapSet.new(reactor.inputs)
provided_input_names = inputs |> Map.keys() |> MapSet.new()
if MapSet.subset?(valid_input_names, provided_input_names) do
{:ok, Map.take(inputs, reactor.inputs)}
else
missing_inputs =
valid_input_names
|> MapSet.difference(provided_input_names)
|> sentence(&"`#{inspect(&1)}`", ", ", " and ")
{:error,
ArgumentError.exception(
message: "Reactor is missing the following inputs; #{missing_inputs}"
)}
end
end
end