improvement: Allow entire step behaviour to be defined in the DSL. (#18)

This commit is contained in:
James Harton 2023-06-16 14:39:29 +12:00 committed by GitHub
parent e612c36993
commit 8f0248857a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 493 additions and 98 deletions

View file

@ -4,16 +4,19 @@ spark_locals_without_parens = [
argument: 2,
argument: 3,
async?: 1,
compensate: 1,
compose: 2,
compose: 3,
input: 1,
input: 2,
max_retries: 1,
return: 1,
run: 1,
step: 1,
step: 2,
step: 3,
transform: 1
transform: 1,
undo: 1
]
[

View file

@ -19,7 +19,7 @@ defmodule Reactor.Argument.Templates do
step :greet do
# here: --------↓↓↓↓↓
argument :name, input(:name)
impl fn
run fn
%{name: nil}, _, _ -> {:ok, "Hello, World!"}
%{name: name}, _, _ -> {:ok, "Hello, #{name}!"}
end
@ -42,7 +42,7 @@ defmodule Reactor.Argument.Templates do
use Reactor
step :whom do
impl fn ->
run fn ->
{:ok, Enum.random(["Marty", "Doc", "Jennifer", "Lorraine", "George", nil])}
end
end
@ -50,7 +50,7 @@ defmodule Reactor.Argument.Templates do
step :greet do
# here: --------↓↓↓↓↓↓
argument :name, result(:whom)
impl fn
run fn
%{name: nil}, _, _ -> {:ok, "Hello, World!"}
%{name: name}, _, _ -> {:ok, "Hello, #{name}!"}
end
@ -79,7 +79,7 @@ defmodule Reactor.Argument.Templates do
# here: -------↓↓↓↓↓
argument :rhs, value(3)
impl fn args, _, _ ->
run fn args, _, _ ->
{:ok, args.lhs * args.rhs}
end
end

View file

@ -123,7 +123,7 @@ defmodule Reactor.Builder.Compose do
],
name: name,
async?: true,
impl: {Step.AnonFn, fun: &{:ok, Map.fetch!(&1, :value)}},
impl: {Step.AnonFn, run: &{:ok, Map.fetch!(&1, :value)}},
max_retries: 0,
ref: make_ref()
}}

View file

@ -1,7 +1,7 @@
defmodule Reactor.Dsl do
@moduledoc false
alias Reactor.{Argument, Dsl, Input, Step, Template}
alias Reactor.{Dsl, Step, Template}
alias Spark.Dsl.{Entity, Extension, Section}
@transform [
@ -32,7 +32,8 @@ defmodule Reactor.Dsl do
"""
],
args: [:name],
target: Input,
target: Dsl.Input,
identifier: :name,
schema: [
name: [
type: :atom,
@ -78,8 +79,9 @@ defmodule Reactor.Dsl do
"""
],
args: [:name, {:optional, :source}],
target: Argument,
imports: [Argument.Templates],
target: Dsl.Argument,
identifier: :name,
imports: [Dsl.Argument],
schema: [
name: [
type: :atom,
@ -97,7 +99,7 @@ defmodule Reactor.Dsl do
doc: """
What to use as the source of the argument.
See `Reactor.Argument.Templates` for more information.
See `Reactor.Dsl.Argument` for more information.
"""
],
transform:
@ -130,14 +132,15 @@ defmodule Reactor.Dsl do
step :hash_password do
argument :password, input(:password)
impl fn %{password: password}, _ ->
run fn %{password: password}, _ ->
{:ok, Bcrypt.hash_pwd_salt(password)}
end
end
"""
],
args: [:name, {:optional, :impl}],
target: Step,
target: Dsl.Step,
identifier: :name,
no_depend_modules: [:impl],
entities: [arguments: [@argument]],
schema: [
@ -152,17 +155,40 @@ defmodule Reactor.Dsl do
"""
],
impl: [
type: {:spark_function_behaviour, Step, {Step.AnonFn, 3}},
required: true,
type: {:or, [{:spark_behaviour, Step}, nil]},
required: false,
doc: """
The step implementation.
The implementation can be either a module which implements the
`Reactor.Step` behaviour or an anonymous function or function capture
with an arity of 2.
Provides an implementation for the step with the named module. The
module must implement the `Reactor.Step` behaviour.
"""
],
run: [
type: {:mfa_or_fun, 2},
required: false,
doc: """
Provide an anonymous function which implements the `run/3` callback.
Note that steps which are implemented as functions cannot be
compensated or undone.
You cannot provide this option at the same time as the `impl` argument.
"""
],
undo: [
type: {:mfa_or_fun, 3},
required: false,
doc: """
Provide an anonymous function which implements the `undo/4` callback.
You cannot provide this option at the same time as the `impl` argument.
"""
],
compensate: [
type: {:mfa_or_fun, 3},
required: false,
doc: """
Provide an anonymous function which implements the `undo/4` callback.
You cannot provide this option at the same time as the `impl` argument.
"""
],
max_retries: [
@ -205,6 +231,7 @@ defmodule Reactor.Dsl do
""",
args: [:name, :reactor],
target: Dsl.Compose,
identifier: :name,
no_depend_modules: [:reactor],
entities: [arguments: [@argument]],
schema: [

101
lib/reactor/dsl/argument.ex Normal file
View file

@ -0,0 +1,101 @@
defmodule Reactor.Dsl.Argument do
@moduledoc """
The struct used to store argument DSL entities.
"""
defstruct name: nil, source: nil, transform: nil, __identifier__: nil
alias Reactor.Template
@type t :: %__MODULE__{
name: atom,
source: Template.Input.t() | Template.Result.t() | Template.Value.t(),
transform: nil | (any -> any) | {module, keyword} | mfa,
__identifier__: any
}
@doc ~S"""
The `input` template helper for the Reactor DSL.
## Example
```elixir
defmodule ExampleReactor do
use Reactor
input :name
step :greet do
# here: --------↓↓↓↓↓
argument :name, input(:name)
run fn
%{name: nil}, _, _ -> {:ok, "Hello, World!"}
%{name: name}, _, _ -> {:ok, "Hello, #{name}!"}
end
end
end
```
"""
@spec input(atom) :: Template.Input.t()
def input(input_name) do
%Template.Input{name: input_name}
end
@doc ~S"""
The `result` template helper for the Reactor DSL.
## Example
```elixir
defmodule ExampleReactor do
use Reactor
step :whom do
run fn ->
{:ok, Enum.random(["Marty", "Doc", "Jennifer", "Lorraine", "George", nil])}
end
end
step :greet do
# here: --------↓↓↓↓↓↓
argument :name, result(:whom)
run fn
%{name: nil}, _, _ -> {:ok, "Hello, World!"}
%{name: name}, _, _ -> {:ok, "Hello, #{name}!"}
end
end
end
```
"""
@spec result(atom) :: Template.Result.t()
def result(link_name) do
%Template.Result{name: link_name}
end
@doc ~S"""
The `value` template helper for the Reactor DSL.
## Example
```elixir
defmodule ExampleReactor do
use Reactor
input :number
step :times_three do
argument :lhs, input(:number)
# here: -------↓↓↓↓↓
argument :rhs, value(3)
run fn args, _, _ ->
{:ok, args.lhs * args.rhs}
end
end
end
```
"""
@spec value(any) :: Template.Value.t()
def value(value) do
%Template.Value{value: value}
end
end

View file

@ -1,9 +1,9 @@
defmodule Reactor.Input do
defmodule Reactor.Dsl.Input do
@moduledoc """
The struct used to store input DSL entities.
"""
defstruct name: nil, transform: nil
defstruct name: nil, transform: nil, __identifier__: nil
@type t :: %__MODULE__{name: any, transform: {module, keyword}}
@type t :: %__MODULE__{name: any, transform: {module, keyword}, __identifier__: any}
end

33
lib/reactor/dsl/step.ex Normal file
View file

@ -0,0 +1,33 @@
defmodule Reactor.Dsl.Step do
@moduledoc """
The struct used to store step DSL entities.
"""
defstruct arguments: [],
async?: true,
compensate: nil,
impl: nil,
max_retries: :infinity,
name: nil,
run: nil,
transform: nil,
undo: nil,
__identifier__: nil
@type t :: %__MODULE__{
arguments: [Reactor.Argument.t()],
async?: boolean,
compensate:
nil | (any, Reactor.inputs(), Reactor.context() -> :ok | :retry | {:continue, any}),
impl: module | {module, keyword},
max_retries: non_neg_integer() | :infinity,
name: atom,
run:
nil
| (Reactor.inputs(), Reactor.context() ->
{:ok, any} | {:ok, any, [Reactor.Step.t()]} | {:halt | :error, any}),
transform: nil | (any -> any),
undo: nil | (any, Reactor.inputs(), Reactor.context() -> :ok | :retry | {:error, any}),
__identifier__: any
}
end

View file

@ -1,13 +1,15 @@
defmodule Reactor.Dsl.Transformer do
@moduledoc false
alias Reactor.{Dsl.Compose, Step}
alias Spark.{Dsl, Dsl.Transformer, Error.DslError}
alias Reactor.{Dsl, Step}
alias Spark.{Dsl.Transformer, Error.DslError}
import Reactor.Utils
use Transformer
@doc false
@spec transform(Dsl.t()) :: {:ok, Dsl.t()} | {:error, DslError.t()}
@spec transform(Spark.Dsl.t()) :: {:ok, Spark.Dsl.t()} | {:error, DslError.t()}
def transform(dsl_state) do
with {:ok, step_names} <- step_names(dsl_state),
with {:ok, dsl_state} <- rewrite_step_impls(dsl_state),
{:ok, step_names} <- step_names(dsl_state),
{:ok, dsl_state} <- maybe_set_return(dsl_state, step_names) do
validate_return(dsl_state, step_names)
end
@ -16,7 +18,7 @@ defmodule Reactor.Dsl.Transformer do
defp step_names(dsl_state) do
dsl_state
|> Transformer.get_entities([:reactor])
|> Enum.filter(&(is_struct(&1, Step) || is_struct(&1, Compose)))
|> Enum.filter(&(is_struct(&1, Dsl.Step) || is_struct(&1, Dsl.Compose)))
|> Enum.map(& &1.name)
|> case do
[] ->
@ -32,6 +34,60 @@ defmodule Reactor.Dsl.Transformer do
end
end
defp rewrite_step_impls(dsl_state) do
dsl_state
|> Transformer.get_entities([:reactor])
|> Enum.filter(&is_struct(&1, Dsl.Step))
|> reduce_while_ok(dsl_state, fn
step, dsl_state when is_nil(step.impl) and is_nil(step.run) ->
{:error,
DslError.exception(
module: Transformer.get_persisted(dsl_state, :module),
path: [:reactor, :step, step.name],
message: "Step has no implementation"
)}
step, dsl_state when not is_nil(step.impl) and not is_nil(step.run) ->
{:error,
DslError.exception(
module: Transformer.get_persisted(dsl_state, :module),
path: [:reactor, :step, step.name],
message: "Step has both an implementation module and a run function"
)}
step, dsl_state when not is_nil(step.impl) and not is_nil(step.compensate) ->
{:error,
DslError.exception(
module: Transformer.get_persisted(dsl_state, :module),
path: [:reactor, :step, step.name],
message: "Step has both an implementation module and a compensate function"
)}
step, dsl_state when not is_nil(step.impl) and not is_nil(step.undo) ->
{:error,
DslError.exception(
module: Transformer.get_persisted(dsl_state, :module),
path: [:reactor, :step, step.name],
message: "Step has both an implementation module and a undo function"
)}
step, dsl_state
when is_nil(step.run) and is_nil(step.compensate) and is_nil(step.undo) and
not is_nil(step.impl) ->
{:ok, dsl_state}
step, dsl_state ->
{:ok,
Transformer.replace_entity(dsl_state, [:reactor], %{
step
| impl: {Step.AnonFn, run: step.run, compensate: step.compensate, undo: step.undo},
run: nil,
compensate: nil,
undo: nil
})}
end)
end
defp maybe_set_return(dsl_state, step_names) do
case Transformer.get_option(dsl_state, [:reactor], :return) do
nil ->

View file

@ -4,7 +4,7 @@ defmodule Reactor.Info do
"""
use Spark.InfoGenerator, sections: [:reactor], extension: Reactor.Dsl
alias Reactor.{Builder, Dsl.Compose, Input, Step}
alias Reactor.{Argument, Builder, Dsl}
import Reactor.Utils
@doc """
@ -34,17 +34,29 @@ defmodule Reactor.Info do
module
|> reactor()
|> reduce_while_ok(Builder.new(module), fn
input, reactor when is_struct(input, Input) ->
input, reactor when is_struct(input, Dsl.Input) ->
Builder.add_input(reactor, input.name, input.transform)
step, reactor when is_struct(step, Step) ->
Builder.add_step(reactor, step.name, step.impl, step.arguments,
step, reactor when is_struct(step, Dsl.Step) ->
arguments =
Enum.map(step.arguments, fn
argument when is_struct(argument, Dsl.Argument) ->
argument
|> Map.from_struct()
|> Map.take(~w[name source transform]a)
|> then(&struct(Argument, &1))
otherwise ->
otherwise
end)
Builder.add_step(reactor, step.name, step.impl, arguments,
async?: step.async?,
max_retries: step.max_retries,
transform: step.transform
)
compose, reactor when is_struct(compose, Compose) ->
compose, reactor when is_struct(compose, Dsl.Compose) ->
Builder.compose(reactor, compose.name, compose.reactor, compose.arguments)
end)
end

View file

@ -10,7 +10,7 @@ defmodule Reactor.Step do
context: %{},
impl: nil,
name: nil,
max_retries: :infinite,
max_retries: :infinity,
ref: nil,
transform: nil

View file

@ -12,24 +12,62 @@ defmodule Reactor.Step.AnonFn do
@impl true
@spec run(Reactor.inputs(), Reactor.context(), keyword) :: {:ok | :error, any}
def run(arguments, context, options) do
case Keyword.pop(options, :fun) do
{fun, _opts} when is_function(fun, 1) ->
case Keyword.fetch!(options, :run) do
fun when is_function(fun, 1) ->
fun.(arguments)
{fun, _opts} when is_function(fun, 2) ->
fun when is_function(fun, 2) ->
fun.(arguments, context)
{fun, opts} when is_function(fun, 3) ->
fun.(arguments, context, opts)
{{m, f, a}, opts} when is_atom(m) and is_atom(f) and is_list(a) ->
apply(m, f, [arguments | [context | [opts | a]]])
{nil, opts} ->
raise ArgumentError,
message: "Invalid options given to `run/3` callback: `#{inspect(opts)}`"
{m, f, a} when is_atom(m) and is_atom(f) and is_list(a) ->
apply(m, f, [arguments, context] ++ a)
end
rescue
error -> {:error, error}
end
@doc false
@impl true
@spec compensate(any, Reactor.inputs(), Reactor.context(), keyword) ::
{:continue, any} | :ok | :retry
def compensate(reason, arguments, context, options) do
case Keyword.fetch(options, :compensate) do
{:ok, fun} when is_function(fun, 1) ->
fun.(reason)
{:ok, fun} when is_function(fun, 2) ->
fun.(reason, arguments)
{:ok, fun} when is_function(fun, 3) ->
fun.(reason, arguments, context)
{:ok, {m, f, a}} when is_atom(m) and is_atom(f) and is_list(a) ->
apply(m, f, [reason, arguments, context] ++ a)
_ ->
:ok
end
end
@doc false
@impl true
@spec undo(any, Reactor.inputs(), Reactor.context(), keyword) :: :ok | :retry | {:error, any}
def undo(value, arguments, context, options) do
case Keyword.fetch(options, :undo) do
{:ok, fun} when is_function(fun, 1) ->
fun.(value)
{:ok, fun} when is_function(fun, 2) ->
fun.(value, arguments)
{:ok, fun} when is_function(fun, 3) ->
fun.(value, arguments, context)
{:ok, {m, f, a}} when is_atom(m) and is_atom(f) and is_list(a) ->
apply(m, f, [value, arguments, context] ++ a)
_ ->
:ok
end
end
end

View file

@ -89,7 +89,7 @@ defmodule Reactor.Step.Compose do
defp create_recursion_step(reactor, name) do
Builder.new_step(
name,
{Step.AnonFn, fun: fn args, _, _ -> {:ok, args.value} end},
{Step.AnonFn, run: fn args, _, _ -> {:ok, args.value} end},
[value: {:result, {__MODULE__, name, reactor.return}}],
max_retries: 0
)

View file

@ -205,10 +205,10 @@ defmodule Reactor.Builder.ComposeTest do
composed_reactor =
Builder.new()
|> Builder.add_input!(:user)
|> Builder.add_step!(:first_name, {Step.AnonFn, fun: &Map.fetch(&1.user, :first_name)},
|> Builder.add_step!(:first_name, {Step.AnonFn, run: &Map.fetch(&1.user, :first_name)},
user: {:input, :user}
)
|> Builder.add_step!(:last_name, {Step.AnonFn, fun: &Map.fetch(&1.user, :last_name)},
|> Builder.add_step!(:last_name, {Step.AnonFn, run: &Map.fetch(&1.user, :last_name)},
user: {:input, :user}
)
|> Builder.compose!(:shout, shouty_reactor,

View file

@ -28,13 +28,13 @@ defmodule Reactor.Builder.InputTest do
test "when the input has a transform impl the input and a transform step are added to the reactor" do
assert {:ok, reactor} =
Builder.new()
|> Input.add_input(:marty, {Step.Transform, fun: &Function.identity/1})
|> Input.add_input(:marty, {Step.Transform, run: &Function.identity/1})
assert :marty in reactor.inputs
assert [step] = reactor.steps
assert step.name == {:__reactor__, :transform, :input, :marty}
assert step.impl == {Step.Transform, fun: &Function.identity/1}
assert step.impl == {Step.Transform, run: &Function.identity/1}
end
test "when the transform is not valid, it returns an error" do

View file

@ -205,10 +205,10 @@ defmodule Reactor.BuilderTest do
composite_reactor =
new()
|> add_input!(:user)
|> add_step!(:first_name, {Step.AnonFn, fun: &Map.fetch(&1.user, :first_name)},
|> add_step!(:first_name, {Step.AnonFn, run: &Map.fetch(&1.user, :first_name)},
user: {:input, :user}
)
|> add_step!(:last_name, {Step.AnonFn, fun: &Map.fetch(&1.user, :last_name)},
|> add_step!(:last_name, {Step.AnonFn, run: &Map.fetch(&1.user, :last_name)},
user: {:input, :user}
)
|> compose!(:shout, shouty_reactor,

141
test/reactor/dsl_test.exs Normal file
View file

@ -0,0 +1,141 @@
defmodule Reactor.DslTest do
@moduledoc false
use ExUnit.Case, async: true
alias Example.Step.Greeter
alias Reactor.{Info, Step}
alias Spark.Error.DslError
describe "transforming steps" do
test "steps with an implementation module compile correctly" do
defmodule StepWithImplReactor do
@moduledoc false
use Reactor
input :whom
step :example, Greeter do
argument :whom, input(:whom)
end
end
step =
StepWithImplReactor
|> Info.to_struct!()
|> Map.get(:steps, [])
|> List.first()
assert step.name == :example
assert step.impl == {Greeter, []}
end
test "steps with function implementations compile correctly" do
defmodule StepWithFnsReactor do
@moduledoc false
use Reactor
input :whom
step :example do
argument :whom, input(:whom)
run(fn %{whom: whom}, _ ->
{:ok, "Hello, #{whom || "World"}!"}
end)
undo(fn _reason, _, _ ->
:ok
end)
compensate(fn _result, _, _ ->
:ok
end)
end
end
step =
StepWithFnsReactor
|> Info.to_struct!()
|> Map.get(:steps, [])
|> List.first()
assert step.name == :example
assert {Step.AnonFn, opts} = step.impl
assert is_function(opts[:run], 2)
assert is_function(opts[:compensate], 3)
assert is_function(opts[:undo], 3)
end
test "steps with no implementation fail to compile" do
assert_raise DslError, ~r/no implementation/, fn ->
defmodule EmptyStepReactor do
@moduledoc false
use Reactor
input :whom
step :example do
argument :whom, input(:whom)
end
end
end
end
test "steps with impl and run fail to compile" do
assert_raise DslError, ~r/both an implementation module and a run function/, fn ->
defmodule DoubleImplRunReactor do
@moduledoc false
use Reactor
input :whom
step :example, Greeter do
argument :whom, input(:whom)
run(fn %{whom: whom}, _ ->
{:ok, "Hello, #{whom || "World"}!"}
end)
end
end
end
end
test "steps with impl and undo fail to compile" do
assert_raise DslError, ~r/both an implementation module and a undo function/, fn ->
defmodule DoubleImplUndoReactor do
@moduledoc false
use Reactor
input :whom
step :example, Greeter do
argument :whom, input(:whom)
undo(fn _reason, _, _ ->
:ok
end)
end
end
end
end
test "steps with impl and compensate fail to compile" do
assert_raise DslError, ~r/both an implementation module and a compensate function/, fn ->
defmodule DoubleImplCompensateReactor do
@moduledoc false
use Reactor
input :whom
step :example, Greeter do
argument :whom, input(:whom)
compensate(fn _result, _, _ ->
:ok
end)
end
end
end
end
end
end

View file

@ -12,7 +12,7 @@ defmodule Reactor.ExecutorTest do
step :atom_to_string do
argument :name, input(:name)
impl(fn %{name: name}, _, _ ->
run(fn %{name: name}, _ ->
{:ok, Atom.to_string(name)}
end)
@ -22,7 +22,7 @@ defmodule Reactor.ExecutorTest do
step :upcase do
argument :name, result(:atom_to_string)
impl(fn %{name: name} = args, _, _ ->
run(fn %{name: name}, _ ->
{:ok, String.upcase(name)}
end)
@ -45,25 +45,25 @@ defmodule Reactor.ExecutorTest do
use Reactor
step :a do
impl(fn _, _, _ ->
run(fn _, _ ->
{:ok, self()}
end)
end
step :b do
impl(fn _, _, _ ->
run(fn _, _ ->
{:ok, self()}
end)
end
step :c do
impl(fn _, _, _ ->
run(fn _, _ ->
{:ok, self()}
end)
end
step :d do
impl(fn _, _, _ ->
run(fn _, _ ->
{:ok, self()}
end)
end
@ -74,7 +74,7 @@ defmodule Reactor.ExecutorTest do
argument :c, result(:c)
argument :d, result(:d)
impl(fn args, _, _ ->
run(fn args, _ ->
{:ok, Map.values(args)}
end)
end
@ -103,7 +103,7 @@ defmodule Reactor.ExecutorTest do
step :atom_to_string do
argument :name, input(:name)
impl(fn %{name: name}, _, _ ->
run(fn %{name: name}, _ ->
{:halt, Atom.to_string(name)}
end)
end
@ -111,7 +111,7 @@ defmodule Reactor.ExecutorTest do
step :upcase do
argument :name, result(:atom_to_string)
impl(fn %{name: name}, _, _ ->
run(fn %{name: name}, _ ->
{:ok, String.upcase(name)}
end)
end
@ -252,7 +252,7 @@ defmodule Reactor.ExecutorTest do
transform & &1.year
end
impl(fn args, _, _ ->
run(fn args, _ ->
{:ok, "#{args.whom} in #{args.when}"}
end)
end
@ -281,7 +281,7 @@ defmodule Reactor.ExecutorTest do
transform &%{whom: "#{&1.whom.first_name} #{&1.whom.last_name}", when: &1.when.year}
impl(fn args, _, _ ->
run(fn args, _ ->
{:ok, "#{args.whom} in #{args.when}"}
end)
end

View file

@ -8,43 +8,27 @@ defmodule Reactor.Step.AnonFnTest do
end
describe "run/3" do
test "it can handle 1 arity anonymous functions" do
fun = fn arguments ->
arguments.first_name
end
assert :marty = run(%{first_name: :marty}, %{}, fun: fun)
end
test "it can handle 2 arity anonymous functions" do
fun = fn arguments, _ ->
arguments.first_name
end
assert :marty = run(%{first_name: :marty}, %{}, fun: fun)
end
test "it can handle 3 arity anonymous functions" do
fun = fn arguments, _, _ ->
arguments.first_name
end
assert :marty = run(%{first_name: :marty}, %{}, fun: fun)
assert :marty = run(%{first_name: :marty}, %{}, run: fun)
end
test "it can handle an MFA" do
assert :marty = run(%{first_name: :marty}, %{}, fun: {__MODULE__, :example, []})
assert :marty = run(%{first_name: :marty}, %{}, run: {__MODULE__, :example, []})
end
test "it rescues errors" do
fun = fn _, _ -> raise "Marty" end
assert {:error, error} = run(%{}, %{}, fun: fun)
assert {:error, error} = run(%{}, %{}, run: fun)
assert Exception.message(error) =~ "Marty"
end
end
def example(arguments, _context, _options) do
def example(arguments, _context) do
arguments.first_name
end
end

View file

@ -40,7 +40,7 @@ defmodule Reactor.Step.ComposeWrapperTest do
assert {:error, error} =
Step.ComposeWrapper.run(%{}, %{current_step: %{name: :foo}},
prefix: [],
original: {Step.AnonFn, fun: fn args -> {:ok, args.a + 1} end}
original: {Step.AnonFn, run: fn args -> {:ok, args.a + 1} end}
)
assert Exception.message(error) =~ ~r/invalid `prefix` option/i
@ -50,7 +50,7 @@ defmodule Reactor.Step.ComposeWrapperTest do
assert {:error, error} =
Step.ComposeWrapper.run(%{}, %{current_step: %{name: :foo}},
prefix: :marty,
original: {Step.AnonFn, fun: fn args -> {:ok, args.a + 1} end}
original: {Step.AnonFn, run: fn args -> {:ok, args.a + 1} end}
)
assert Exception.message(error) =~ ~r/invalid `prefix` option/i
@ -60,7 +60,7 @@ defmodule Reactor.Step.ComposeWrapperTest do
assert {:ok, 2} =
Step.ComposeWrapper.run(%{a: 1}, %{current_step: %{name: :foo}},
prefix: [:a, :b],
original: {Step.AnonFn, fun: fn args -> {:ok, args.a + 1} end}
original: {Step.AnonFn, run: fn args -> {:ok, args.a + 1} end}
)
end
@ -68,7 +68,7 @@ defmodule Reactor.Step.ComposeWrapperTest do
assert {:error, :wat} =
Step.ComposeWrapper.run(%{}, %{current_step: %{name: :foo}},
prefix: [:a, :b],
original: {Step.AnonFn, fun: fn _ -> {:error, :wat} end}
original: {Step.AnonFn, run: fn _ -> {:error, :wat} end}
)
end
@ -76,14 +76,14 @@ defmodule Reactor.Step.ComposeWrapperTest do
assert {:halt, :wat} =
Step.ComposeWrapper.run(%{}, %{current_step: %{name: :foo}},
prefix: [:a, :b],
original: {Step.AnonFn, fun: fn _ -> {:halt, :wat} end}
original: {Step.AnonFn, run: fn _ -> {:halt, :wat} end}
)
end
test "when the original step returns new dynamic steps, it rewrites them" do
[new_c, new_d] = [
Builder.new_step!(:c, {Step.AnonFn, fun: fn args -> {:ok, args.b} end}, b: {:input, :b}),
Builder.new_step!(:d, {Step.AnonFn, fun: fn args -> {:ok, args.c + 1} end},
Builder.new_step!(:c, {Step.AnonFn, run: fn args -> {:ok, args.b} end}, b: {:input, :b}),
Builder.new_step!(:d, {Step.AnonFn, run: fn args -> {:ok, args.c + 1} end},
c: {:result, :c}
)
]
@ -91,7 +91,7 @@ defmodule Reactor.Step.ComposeWrapperTest do
assert {:ok, _, [rewritten_c, rewritten_d]} =
Step.ComposeWrapper.run(%{}, %{current_step: %{name: :foo}},
prefix: [:a, :b],
original: {Step.AnonFn, fun: fn _ -> {:ok, nil, [new_c, new_d]} end}
original: {Step.AnonFn, run: fn _ -> {:ok, nil, [new_c, new_d]} end}
)
assert rewritten_c.name == {:a, :b, new_c.name}

View file

@ -15,17 +15,17 @@ defmodule ReactorTest do
step :split do
argument :name, input(:name)
impl(fn %{name: name}, _, _ -> {:ok, String.split(name)} end)
run(fn %{name: name}, _ -> {:ok, String.split(name)} end)
end
step :reverse do
argument :chunks, result(:split)
impl(fn %{chunks: chunks}, _, _ -> {:ok, Enum.reverse(chunks)} end)
run(fn %{chunks: chunks}, _ -> {:ok, Enum.reverse(chunks)} end)
end
step :join do
argument :chunks, result(:reverse)
impl(fn %{chunks: chunks}, _, _ -> {:ok, Enum.join(chunks, " ")} end)
run(fn %{chunks: chunks}, _ -> {:ok, Enum.join(chunks, " ")} end)
end
end

View file

@ -8,7 +8,7 @@ defmodule Example.MapReduceReactor do
producer :split_to_words do
argument :words, input(:words)
impl(fn %{words: words}, _, _ ->
run(fn %{words: words}, _, _ ->
stream =
words
|> split_into_stream()
@ -23,7 +23,7 @@ defmodule Example.MapReduceReactor do
step :count_batch do
argument :batch, element(:count_batches)
impl fn %{batch: batch}, _, _ ->
run fn %{batch: batch}, _, _ ->
{:ok, Enum.frequencies(batch)}
end
end
@ -32,7 +32,7 @@ defmodule Example.MapReduceReactor do
reduce :into_result do
over result(:count_batches)
impl fn %{input: stream} ->
run fn %{input: stream} ->
end
end