Packages
reactor
0.5.2
1.0.2
1.0.1
1.0.0
0.17.0
0.16.0
0.15.6
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.0
0.13.3
0.13.2
0.13.1
0.13.0
0.12.1
0.12.0
0.11.0
0.10.3
0.10.2
0.10.1
0.10.0
0.9.1
0.9.0
0.8.5
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.0
0.6.0
0.5.2
0.5.1
0.5.0
0.4.1
0.4.0
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.0
An asynchronous, graph-based execution engine
Current section
Files
Jump to
Current section
Files
lib/reactor/step/compose_wrapper.ex
defmodule Reactor.Step.ComposeWrapper do
@moduledoc """
When doing run-time composition of Reactors we need to dynamically rewrite any
dynamically emitted steps to have the correct namespace.
Yes, this gets hairy, fast.
This is dynamically injected into steps by `Reactor.Step.Compose` - you
probably don't want to use this unless you're sure what you're doing.
## Options
* `original` - the original value of the Step's `impl` key.
* `prefix` - a list of values to be placed in the `name` before the original value.
"""
use Reactor.Step
alias Reactor.Argument
import Reactor.Argument, only: :macros
import Reactor.Utils
@doc false
@impl true
def run(arguments, context, options) do
with {:ok, impl} <- validate_original_option(options, context.current_step),
{:ok, prefix} <- validate_prefix_option(options, context.current_step) do
case do_run(impl, arguments, context) do
{:ok, value} -> {:ok, value}
{:ok, value, steps} -> {:ok, value, rewrite_steps(steps, prefix)}
{stop, reason} when stop in ~w[halt error]a -> {stop, reason}
end
end
end
@doc false
@impl true
def compensate(reason, arguments, context, options) do
case get_original_option(options, context.current_step) do
{:ok, {impl, opts}} -> impl.compensate(reason, arguments, context, opts)
{:ok, impl} -> impl.compensate(reason, arguments, context, [])
{:error, _} -> :ok
end
rescue
UndefinedFunctionError -> :ok
end
@doc false
@impl true
def undo(value, arguments, context, options) do
case get_original_option(options, context.current_step) do
{:ok, {impl, opts}} -> impl.undo(value, arguments, context, opts)
{:ok, impl} -> impl.undo(value, arguments, context, [])
{:error, reason} -> {:error, reason}
end
rescue
UndefinedFunctionError -> :ok
end
defp validate_original_option(options, current_step) do
with {:ok, original} <- get_original_option(options, current_step),
{module, opts} <- get_module_and_options(original) do
if Spark.implements_behaviour?(module, Reactor.Step) do
{:ok, {module, opts}}
else
{:error,
argument_error(
:options,
"Step `#{current_step.name}` module `#{inspect(module)}` does not implement the `Reactor.Step` behaviour.",
opts
)}
end
end
end
defp get_original_option(options, current_step) do
with :error <- Keyword.fetch(options, :original) do
{:error,
argument_error(
:options,
"Step `#{current_step.name}` is missing the `original` option.",
options
)}
end
end
defp validate_prefix_option(options, current_step) do
case Keyword.fetch(options, :prefix) do
{:ok, [_ | _] = prefix} ->
{:ok, prefix}
:error ->
{:error,
argument_error(
:options,
"Step `#{current_step.name}` has missing `prefix` option.",
options
)}
_ ->
{:error,
argument_error(
:options,
"Step `#{current_step.name}` has invalid `prefix` option.",
options
)}
end
end
defp get_module_and_options(impl) when is_atom(impl), do: {impl, []}
defp get_module_and_options({impl, options}) when is_atom(impl) and is_list(options),
do: {impl, options}
defp do_run({module, options}, arguments, context) when is_atom(module) and is_list(options),
do: module.run(arguments, context, options)
defp rewrite_steps(steps, prefix) do
steps
|> Enum.map(fn step ->
name =
prefix
|> Enum.concat([step.name])
|> List.to_tuple()
arguments = Enum.map(step.arguments, &rewrite_argument(&1, prefix))
%{step | name: name, arguments: arguments}
end)
end
defp rewrite_argument(argument, prefix) when is_from_result(argument) do
source =
prefix
|> Enum.concat([argument.source.name])
|> List.to_tuple()
Argument.from_result(argument.name, source)
end
defp rewrite_argument(argument, _prefix)
when is_from_input(argument) or is_from_value(argument),
do: argument
end