Packages
reactor
0.17.0
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/dsl/recurse.ex
defmodule Reactor.Dsl.Recurse do
@moduledoc """
The `recurse` DSL entity struct.
See the `d:Reactor.recurse`.
"""
defstruct __identifier__: nil,
arguments: [],
async?: nil,
description: nil,
guards: [],
name: nil,
reactor: nil,
max_iterations: nil,
exit_condition: nil,
__spark_metadata__: nil
alias Reactor.{Builder, Dsl}
alias Spark.Dsl.Transformer
@type t :: %Dsl.Recurse{
__identifier__: any,
arguments: [Dsl.Argument.t() | Dsl.WaitFor.t()],
async?: nil | boolean,
description: nil | String.t(),
guards: [Dsl.Where.t() | Dsl.Guard.t()],
name: any,
reactor: module | Reactor.t(),
max_iterations: nil | pos_integer(),
exit_condition: nil | (any -> boolean),
__spark_metadata__: Spark.Dsl.Entity.spark_meta()
}
@doc false
def __entity__,
do: %Spark.Dsl.Entity{
name: :recurse,
describe: """
Execute a reactor recursively until an exit condition is met or maximum iterations are reached.
Allows the output of one iteration to become the input of the next. The reactor must have
input/output compatibility to ensure that the outputs from iteration i can be fed into
iteration i+1.
""",
args: [:name, :reactor],
target: Dsl.Recurse,
identifier: :name,
no_depend_modules: [:reactor],
entities: [
arguments: [Dsl.Argument.__entity__(), Dsl.WaitFor.__entity__()],
guards: [Dsl.Where.__entity__(), Dsl.Guard.__entity__()]
],
recursive_as: :steps,
schema: [
name: [
type: :atom,
required: true,
doc: """
A unique name for the step. Allows the result of the recursed reactor to be depended upon by steps in this reactor.
"""
],
description: [
type: :string,
required: false,
doc: """
An optional description for the step.
"""
],
reactor: [
type: {:or, [{:struct, Reactor}, {:spark, Reactor.Dsl}]},
required: true,
doc: """
The reactor module or struct to recurse on.
"""
],
async?: [
type: :boolean,
required: false,
default: true,
doc: """
Whether the recursed steps should be run asynchronously.
"""
],
max_iterations: [
type: :pos_integer,
required: false,
doc: """
The maximum number of iterations to execute. If not specified, will rely solely on exit_condition.
"""
],
exit_condition: [
type: {:or, [nil, {:mfa_or_fun, 1}]},
required: false,
doc: """
A function that takes the result of an iteration and returns a boolean. When true, the recursion will stop. If not provided, will rely solely on max_iterations.
"""
]
]
}
defimpl Dsl.Build do
alias Spark.{Dsl.Verifier, Error.DslError}
def build(step, reactor) do
Builder.recurse(reactor, step.name, step.reactor, step.arguments,
async?: step.async?,
description: step.description,
guards: step.guards,
max_iterations: step.max_iterations,
exit_condition: step.exit_condition
)
end
def verify(step, dsl_state) do
with :ok <- verify_arguments(step, dsl_state) do
verify_constraints(step, dsl_state)
end
end
defp verify_arguments(step, dsl_state) do
case Builder.Compose.verify_arguments(step.reactor, step.arguments) do
:ok ->
:ok
{:error, {:extra_args, inputs, extra_args}} ->
{:error,
%DslError{
module: Verifier.get_persisted(dsl_state, :module),
path: [:reactor, :recurse, step.name],
message: """
# Extra arguments while recursing Reactors.
The recursed Reactor takes the following inputs:
#{Enum.map_join(inputs, "\n", &" - #{&1}")}
The extra arguments are:
#{Enum.map_join(extra_args, "\n", &" - #{&1}")}
"""
}}
{:error, {:missing_args, inputs, missing_args}} ->
{:error,
%DslError{
module: Verifier.get_persisted(dsl_state, :module),
path: [:reactor, :recurse, step.name],
message: """
# Missing arguments while recursing Reactors.
The recursed Reactor takes the following inputs:
#{Enum.map_join(inputs, "\n", &" - #{&1}")}
The missing arguments are:
#{Enum.map_join(missing_args, "\n", &" - #{&1}")}
"""
}}
end
end
defp verify_constraints(step, dsl_state) do
with :ok <- verify_termination_conditions(step, dsl_state) do
verify_reactor_return(step, dsl_state)
end
end
defp verify_termination_conditions(step, dsl_state) do
if is_nil(step.max_iterations) and is_nil(step.exit_condition) do
{:error,
%DslError{
module: Verifier.get_persisted(dsl_state, :module),
path: [:reactor, :recurse, step.name],
message: """
# Missing constraints for recursion.
You must provide at least one of:
- max_iterations: a maximum number of iterations to prevent infinite loops
- exit_condition: a function that determines when to stop recursion
"""
}}
else
:ok
end
end
defp verify_reactor_return(step, dsl_state) do
return_section = Transformer.get_option(dsl_state, [:reactor], :return)
if is_nil(return_section) do
{:error,
%DslError{
module: Verifier.get_persisted(dsl_state, :module),
path: [:reactor, :recurse, step.name],
message: """
# Missing return value in recursed reactor.
The reactor being recursed must have a return value defined to enable iterative execution.
"""
}}
else
:ok
end
end
end
end