Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions lib/reactor/step/switch.ex
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ defmodule Reactor.Step.Switch do
@type on_option :: {:on, atom}

@doc false
@impl true
@spec run(Reactor.inputs(), Reactor.context(), options) :: {:ok, any} | {:error, any}
def run(arguments, _context, options) do
allow_async? = Keyword.get(options, :allow_async?, true)
Expand All @@ -90,6 +91,19 @@ defmodule Reactor.Step.Switch do
def to_mermaid(step, options),
do: __MODULE__.Mermaid.to_mermaid(step, options)

@doc false
@impl true
def nested_steps(options) do
matches = Keyword.get(options, :matches, [])
default = Keyword.get(options, :default, [])

match_steps =
matches
|> Enum.flat_map(fn {_predicate, steps} -> steps end)

match_steps ++ default
end

defp find_match(matches, value) do
Enum.reduce_while(matches, :no_match, fn {predicate, steps}, :no_match ->
if predicate.(value) do
Expand Down
72 changes: 72 additions & 0 deletions test/reactor/dsl/switch_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -123,4 +123,76 @@ defmodule Reactor.Dsl.SwitchTest do
end) =~ ~r/amscray/
end
end

describe "issue #273 - nested steps can refer to external step results with async execution" do
defmodule SwitchExternalResultAsyncReactor do
@moduledoc false
use Reactor

input :params

step :create_personal_organization do
run fn _, _ ->
Process.sleep(10)
{:ok, %{org_id: "org_123"}}
end
end

step :create_session_token do
run fn _, _ ->
{:ok, "token_abc"}
end
end

switch :populate_scope do
on input(:params)

matches? &(Map.get(&1, "invite_token") != nil) do
step :populate_scope do
argument :user_token, result(:create_session_token)
wait_for :create_personal_organization

run fn %{user_token: token}, _ ->
{:ok, %{token: token, source: :invite}}
end
end
end

default do
step :populate_scope do
argument :user_token, result(:create_session_token)
argument :scope, result(:create_personal_organization)

run fn %{scope: scope, user_token: token}, _ ->
{:ok, %{token: token, org_id: scope.org_id, source: :default}}
end
end
end
end

return :populate_scope
end

test "when switch nested steps depend on external results, they are resolved correctly with async execution" do
assert {:ok, result} =
Reactor.run(SwitchExternalResultAsyncReactor, %{params: %{}}, %{}, async?: true)

assert result.source == :default
assert result.org_id == "org_123"
assert result.token == "token_abc"
end

test "when switch nested steps depend on external results via wait_for, they are resolved correctly" do
assert {:ok, result} =
Reactor.run(
SwitchExternalResultAsyncReactor,
%{params: %{"invite_token" => "inv_123"}},
%{},
async?: true
)

assert result.source == :invite
assert result.token == "token_abc"
end
end
end
37 changes: 37 additions & 0 deletions test/reactor/step/switch_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -117,4 +117,41 @@ defmodule Reactor.Step.SwitchTest do
assert error =~ ~r/no default branch/i
end
end

describe "nested_steps/1" do
test "returns all steps from matches and default", %{matches: matches, default: default} do
options = [matches: matches, default: default, on: :value]
nested = Switch.nested_steps(options)

step_names = Enum.map(nested, & &1.name)
assert :is_nil in step_names
assert :is_false in step_names
assert :is_other in step_names
assert length(nested) == 3
end

test "returns empty list when no matches or default", %{} do
assert [] == Switch.nested_steps(on: :value)
end

test "returns only match steps when no default", %{matches: matches} do
options = [matches: matches, on: :value]
nested = Switch.nested_steps(options)

step_names = Enum.map(nested, & &1.name)
assert :is_nil in step_names
assert :is_false in step_names
refute :is_other in step_names
assert length(nested) == 2
end

test "returns only default steps when no matches", %{default: default} do
options = [default: default, on: :value]
nested = Switch.nested_steps(options)

step_names = Enum.map(nested, & &1.name)
assert :is_other in step_names
assert length(nested) == 1
end
end
end