floe 0.14.0 → 0.15.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Floe
4
+ class Workflow
5
+ module States
6
+ module ChildWorkflowMixin
7
+ def run_nonblock!(context)
8
+ start(context) unless context.state_started?
9
+ step_nonblock!(context)
10
+ return Errno::EAGAIN unless ready?(context)
11
+
12
+ finish(context) if ended?(context)
13
+ end
14
+
15
+ def finish(context)
16
+ if success?(context)
17
+ result = each_child_context(context).map(&:output)
18
+ context.output = process_output(context, result)
19
+ else
20
+ error = parse_error(context)
21
+ retry_state!(context, error) || catch_error!(context, error) || fail_workflow!(context, error)
22
+ end
23
+
24
+ super
25
+ end
26
+
27
+ def ready?(context)
28
+ !context.state_started? || each_child_workflow(context).any? { |wf, ctx| wf.step_nonblock_ready?(ctx) }
29
+ end
30
+
31
+ def wait_until(context)
32
+ each_child_workflow(context).filter_map { |wf, ctx| wf.wait_until(ctx) }.min
33
+ end
34
+
35
+ def waiting?(context)
36
+ each_child_workflow(context).any? { |wf, ctx| wf.waiting?(ctx) }
37
+ end
38
+
39
+ def running?(context)
40
+ !ended?(context)
41
+ end
42
+
43
+ def ended?(context)
44
+ each_child_context(context).all?(&:ended?)
45
+ end
46
+
47
+ def success?(context)
48
+ each_child_context(context).none?(&:failed?)
49
+ end
50
+
51
+ def each_child_context(context)
52
+ context.state[child_context_key].map { |ctx| Context.new(ctx) }
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -9,10 +9,9 @@ module Floe
9
9
  def initialize(workflow, name, payload)
10
10
  super
11
11
 
12
- validate_state!(workflow)
13
-
14
- @choices = payload["Choices"].map.with_index { |choice, i| ChoiceRule.build(workflow, name + ["Choices", i.to_s], choice) }
12
+ @choices = payload["Choices"]&.map&.with_index { |choice, i| ChoiceRule.build(workflow, name + ["Choices", i.to_s], choice) }
15
13
  @default = payload["Default"]
14
+ validate_state!(workflow)
16
15
 
17
16
  @input_path = Path.new(payload.fetch("InputPath", "$"))
18
17
  @output_path = Path.new(payload.fetch("OutputPath", "$"))
@@ -45,12 +44,12 @@ module Floe
45
44
  end
46
45
 
47
46
  def validate_state_choices!
48
- missing_field_error!("Choices") unless payload.key?("Choices")
49
- invalid_field_error!("Choices", nil, "must be a non-empty array") unless payload["Choices"].kind_of?(Array) && !payload["Choices"].empty?
47
+ missing_field_error!("Choices") if @choices.nil?
48
+ invalid_field_error!("Choices", nil, "must be a non-empty array") unless @choices.kind_of?(Array) && !@choices.empty?
50
49
  end
51
50
 
52
51
  def validate_state_default!(workflow)
53
- invalid_field_error!("Default", payload["Default"], "is not found in \"States\"") if payload["Default"] && !workflow_state?(payload["Default"], workflow)
52
+ invalid_field_error!("Default", @default, "is not found in \"States\"") if @default && !workflow_state?(@default, workflow)
54
53
  end
55
54
  end
56
55
  end
@@ -4,9 +4,123 @@ module Floe
4
4
  class Workflow
5
5
  module States
6
6
  class Map < Floe::Workflow::State
7
- def initialize(*)
7
+ include ChildWorkflowMixin
8
+ include InputOutputMixin
9
+ include NonTerminalMixin
10
+ include RetryCatchMixin
11
+
12
+ attr_reader :end, :next, :parameters, :input_path, :output_path, :result_path,
13
+ :result_selector, :retry, :catch, :item_processor, :items_path,
14
+ :item_reader, :item_selector, :item_batcher, :result_writer,
15
+ :max_concurrency, :tolerated_failure_percentage, :tolerated_failure_count
16
+
17
+ def initialize(workflow, name, payload)
18
+ super
19
+
20
+ missing_field_error!("InputProcessor") if payload["ItemProcessor"].nil?
21
+
22
+ @next = payload["Next"]
23
+ @end = !!payload["End"]
24
+ @parameters = PayloadTemplate.new(payload["Parameters"]) if payload["Parameters"]
25
+ @input_path = Path.new(payload.fetch("InputPath", "$"))
26
+ @output_path = Path.new(payload.fetch("OutputPath", "$"))
27
+ @result_path = ReferencePath.new(payload.fetch("ResultPath", "$"))
28
+ @result_selector = PayloadTemplate.new(payload["ResultSelector"]) if payload["ResultSelector"]
29
+ @retry = payload["Retry"].to_a.map { |retrier| Retrier.new(retrier) }
30
+ @catch = payload["Catch"].to_a.map { |catcher| Catcher.new(catcher) }
31
+ @item_processor = ItemProcessor.new(payload["ItemProcessor"], name)
32
+ @items_path = ReferencePath.new(payload.fetch("ItemsPath", "$"))
33
+ @item_reader = payload["ItemReader"]
34
+ @item_selector = payload["ItemSelector"]
35
+ @item_batcher = payload["ItemBatcher"]
36
+ @result_writer = payload["ResultWriter"]
37
+ @max_concurrency = payload["MaxConcurrency"]&.to_i
38
+ @tolerated_failure_percentage = payload["ToleratedFailurePercentage"]&.to_i
39
+ @tolerated_failure_count = payload["ToleratedFailureCount"]&.to_i
40
+
41
+ validate_state!(workflow)
42
+ end
43
+
44
+ def process_input(context)
45
+ input = super
46
+ items_path.value(context, input)
47
+ end
48
+
49
+ def start(context)
8
50
  super
9
- raise NotImplementedError
51
+
52
+ input = process_input(context)
53
+
54
+ context.state["ItemProcessorContext"] = input.map { |item| Context.new({"Execution" => {"Id" => context.execution["Id"]}}, :input => item.to_json).to_h }
55
+ end
56
+
57
+ def end?
58
+ @end
59
+ end
60
+
61
+ def success?(context)
62
+ contexts = each_child_context(context)
63
+ num_failed = contexts.count(&:failed?)
64
+ total = contexts.count
65
+
66
+ return true if num_failed.zero? || total.zero?
67
+ return false if tolerated_failure_count.nil? && tolerated_failure_percentage.nil?
68
+
69
+ # Some have failed, check the tolerated_failure thresholds to see if
70
+ # we should fail the whole state.
71
+ #
72
+ # If either ToleratedFailureCount or ToleratedFailurePercentage are breached
73
+ # then the whole state is considered failed.
74
+ count_tolerated = tolerated_failure_count.nil? || num_failed < tolerated_failure_count
75
+ pct_tolerated = tolerated_failure_percentage.nil? || tolerated_failure_percentage == 100 ||
76
+ ((100 * num_failed / total.to_f) < tolerated_failure_percentage)
77
+
78
+ count_tolerated && pct_tolerated
79
+ end
80
+
81
+ private
82
+
83
+ def step_nonblock!(context)
84
+ each_child_context(context).each do |ctx|
85
+ # If this iteration isn't already running and we can't start any more
86
+ next if !ctx.started? && concurrency_exceeded?(context)
87
+
88
+ item_processor.run_nonblock(ctx) if item_processor.step_nonblock_ready?(ctx)
89
+ end
90
+ end
91
+
92
+ def each_child_workflow(context)
93
+ each_child_context(context).map do |ctx|
94
+ [item_processor, Context.new(ctx)]
95
+ end
96
+ end
97
+
98
+ def concurrency_exceeded?(context)
99
+ max_concurrency && num_running(context) >= max_concurrency
100
+ end
101
+
102
+ def num_running(context)
103
+ each_child_context(context).count(&:running?)
104
+ end
105
+
106
+ def parse_error(context)
107
+ # If ToleratedFailureCount or ToleratedFailurePercentage is present
108
+ # then use States.ExceedToleratedFailureThreshold otherwise
109
+ # take the error from the first failed state
110
+ if tolerated_failure_count || tolerated_failure_percentage
111
+ {"Error" => "States.ExceedToleratedFailureThreshold"}
112
+ else
113
+ each_child_context(context).detect(&:failed?)&.output || {"Error" => "States.Error"}
114
+ end
115
+ end
116
+
117
+ def child_context_key
118
+ "ItemProcessorContext"
119
+ end
120
+
121
+ def validate_state!(workflow)
122
+ validate_state_next!(workflow)
123
+ invalid_field_error!("MaxConcurrency", @max_concurrency, "must be greater than 0") if @max_concurrency && @max_concurrency <= 0
10
124
  end
11
125
  end
12
126
  end
@@ -4,9 +4,72 @@ module Floe
4
4
  class Workflow
5
5
  module States
6
6
  class Parallel < Floe::Workflow::State
7
- def initialize(*)
7
+ include ChildWorkflowMixin
8
+ include InputOutputMixin
9
+ include NonTerminalMixin
10
+ include RetryCatchMixin
11
+
12
+ attr_reader :end, :next, :parameters, :input_path, :output_path, :result_path,
13
+ :result_selector, :retry, :catch, :branches
14
+
15
+ def initialize(workflow, name, payload)
16
+ super
17
+
18
+ missing_field_error!("Branches") if payload["Branches"].nil?
19
+
20
+ @next = payload["Next"]
21
+ @end = !!payload["End"]
22
+ @parameters = PayloadTemplate.new(payload["Parameters"]) if payload["Parameters"]
23
+ @input_path = Path.new(payload.fetch("InputPath", "$"))
24
+ @output_path = Path.new(payload.fetch("OutputPath", "$"))
25
+ @result_path = ReferencePath.new(payload.fetch("ResultPath", "$"))
26
+ @result_selector = PayloadTemplate.new(payload["ResultSelector"]) if payload["ResultSelector"]
27
+ @retry = payload["Retry"].to_a.map { |retrier| Retrier.new(retrier) }
28
+ @catch = payload["Catch"].to_a.map { |catcher| Catcher.new(catcher) }
29
+ @branches = payload["Branches"].map { |branch| Branch.new(branch) }
30
+
31
+ validate_state!(workflow)
32
+ end
33
+
34
+ def start(context)
8
35
  super
9
- raise NotImplementedError
36
+
37
+ input = process_input(context)
38
+
39
+ context.state["BranchContext"] = branches.map { |_branch| Context.new({"Execution" => {"Id" => context.execution["Id"]}}, :input => input.to_json).to_h }
40
+ end
41
+
42
+ def end?
43
+ @end
44
+ end
45
+
46
+ private
47
+
48
+ def step_nonblock!(context)
49
+ each_child_workflow(context).each do |wf, ctx|
50
+ wf.run_nonblock(ctx) if wf.step_nonblock_ready?(ctx)
51
+ end
52
+ end
53
+
54
+ def each_child_workflow(context)
55
+ branches.filter_map.with_index do |branch, i|
56
+ ctx = context.state.dig("BranchContext", i)
57
+ next if ctx.nil?
58
+
59
+ [branch, Context.new(ctx)]
60
+ end
61
+ end
62
+
63
+ def parse_error(context)
64
+ each_child_context(context).detect(&:failed?)&.output || {"Error" => "States.Error"}
65
+ end
66
+
67
+ def child_context_key
68
+ "BranchContext"
69
+ end
70
+
71
+ def validate_state!(workflow)
72
+ validate_state_next!(workflow)
10
73
  end
11
74
  end
12
75
  end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Floe
4
+ class Workflow
5
+ module States
6
+ module RetryCatchMixin
7
+ def find_retrier(error)
8
+ self.retry.detect { |r| r.match_error?(error) }
9
+ end
10
+
11
+ def find_catcher(error)
12
+ self.catch.detect { |c| c.match_error?(error) }
13
+ end
14
+
15
+ def retry_state!(context, error)
16
+ retrier = find_retrier(error["Error"]) if error
17
+ return if retrier.nil?
18
+
19
+ # If a different retrier is hit reset the context
20
+ if !context["State"].key?("RetryCount") || context["State"]["Retrier"] != retrier.error_equals
21
+ context["State"]["RetryCount"] = 0
22
+ context["State"]["Retrier"] = retrier.error_equals
23
+ end
24
+
25
+ context["State"]["RetryCount"] += 1
26
+
27
+ return if context["State"]["RetryCount"] > retrier.max_attempts
28
+
29
+ wait_until!(context, :seconds => retrier.sleep_duration(context["State"]["RetryCount"]))
30
+ context.next_state = context.state_name
31
+ context.output = error
32
+ context.logger.info("Running state: [#{long_name}] with input [#{context.json_input}] got error[#{context.json_output}]...Retry - delay: #{wait_until(context)}")
33
+ true
34
+ end
35
+
36
+ def catch_error!(context, error)
37
+ catcher = find_catcher(error["Error"]) if error
38
+ return if catcher.nil?
39
+
40
+ context.next_state = catcher.next
41
+ context.output = catcher.result_path.set(context.input, error)
42
+ context.logger.info("Running state: [#{long_name}] with input [#{context.json_input}]...CatchError - next state: [#{context.next_state}] output: [#{context.json_output}]")
43
+
44
+ true
45
+ end
46
+
47
+ def fail_workflow!(context, error)
48
+ # next_state is nil, and will be set to nil again in super
49
+ # keeping in here for completeness
50
+ context.next_state = nil
51
+ context.output = error
52
+ context.logger.error("Running state: [#{long_name}] with input [#{context.json_input}]...Complete workflow - output: [#{context.json_output}]")
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -6,6 +6,7 @@ module Floe
6
6
  class Task < Floe::Workflow::State
7
7
  include InputOutputMixin
8
8
  include NonTerminalMixin
9
+ include RetryCatchMixin
9
10
 
10
11
  attr_reader :credentials, :end, :heartbeat_seconds, :next, :parameters,
11
12
  :result_selector, :resource, :timeout_seconds, :retry, :catch,
@@ -60,7 +61,8 @@ module Floe
60
61
  end
61
62
 
62
63
  def running?(context)
63
- return true if waiting?(context)
64
+ return true if waiting?(context)
65
+ return false if finished?(context)
64
66
 
65
67
  runner.status!(context.state["RunnerContext"])
66
68
  runner.running?(context.state["RunnerContext"])
@@ -82,54 +84,6 @@ module Floe
82
84
  runner.success?(context.state["RunnerContext"])
83
85
  end
84
86
 
85
- def find_retrier(error)
86
- self.retry.detect { |r| r.match_error?(error) }
87
- end
88
-
89
- def find_catcher(error)
90
- self.catch.detect { |c| c.match_error?(error) }
91
- end
92
-
93
- def retry_state!(context, error)
94
- retrier = find_retrier(error["Error"]) if error
95
- return if retrier.nil?
96
-
97
- # If a different retrier is hit reset the context
98
- if !context["State"].key?("RetryCount") || context["State"]["Retrier"] != retrier.error_equals
99
- context["State"]["RetryCount"] = 0
100
- context["State"]["Retrier"] = retrier.error_equals
101
- end
102
-
103
- context["State"]["RetryCount"] += 1
104
-
105
- return if context["State"]["RetryCount"] > retrier.max_attempts
106
-
107
- wait_until!(context, :seconds => retrier.sleep_duration(context["State"]["RetryCount"]))
108
- context.next_state = context.state_name
109
- context.output = error
110
- logger.info("Running state: [#{long_name}] with input [#{context.json_input}] got error[#{context.json_output}]...Retry - delay: #{wait_until(context)}")
111
- true
112
- end
113
-
114
- def catch_error!(context, error)
115
- catcher = find_catcher(error["Error"]) if error
116
- return if catcher.nil?
117
-
118
- context.next_state = catcher.next
119
- context.output = catcher.result_path.set(context.input, error)
120
- logger.info("Running state: [#{long_name}] with input [#{context.json_input}]...CatchError - next state: [#{context.next_state}] output: [#{context.json_output}]")
121
-
122
- true
123
- end
124
-
125
- def fail_workflow!(context, error)
126
- # next_state is nil, and will be set to nil again in super
127
- # keeping in here for completeness
128
- context.next_state = nil
129
- context.output = error
130
- logger.error("Running state: [#{long_name}] with input [#{context.json_input}]...Complete workflow - output: [#{context.json_output}]")
131
- end
132
-
133
87
  def parse_error(output)
134
88
  return if output.nil?
135
89
  return output if output.kind_of?(Hash)
data/lib/floe/workflow.rb CHANGED
@@ -4,9 +4,8 @@ require "securerandom"
4
4
  require "json"
5
5
 
6
6
  module Floe
7
- class Workflow
7
+ class Workflow < Floe::WorkflowBase
8
8
  include Logging
9
- include ValidationMixin
10
9
 
11
10
  class << self
12
11
  def load(path_or_io, context = nil, credentials = {}, name = nil)
@@ -19,7 +18,6 @@ module Floe
19
18
 
20
19
  def wait(workflows, timeout: nil, &block)
21
20
  workflows = [workflows] if workflows.kind_of?(self)
22
- logger.info("checking #{workflows.count} workflows...")
23
21
 
24
22
  run_until = Time.now.utc + timeout if timeout.to_i > 0
25
23
  ready = []
@@ -66,29 +64,20 @@ module Floe
66
64
  event, data = queue.pop
67
65
  break if event.nil?
68
66
 
69
- _execution_id, runner_context = data.values_at("execution_id", "runner_context")
70
-
71
- # If the event is for one of our workflows set the updated runner_context
72
- workflows.each do |workflow|
73
- next unless workflow.context.state.dig("RunnerContext", "container_ref") == runner_context["container_ref"]
74
-
75
- workflow.context.state["RunnerContext"] = runner_context
76
- end
77
-
78
- break if queue.empty?
67
+ # break out of the loop if the event is for one of our workflows
68
+ break if queue.empty? || workflows.detect { |wf| wf.execution_id == data["execution_id"] }
79
69
  end
80
70
  ensure
81
71
  sleep_thread&.kill
82
72
  end
83
73
 
84
- logger.info("checking #{workflows.count} workflows...Complete - #{ready.count} ready")
85
74
  ready
86
75
  ensure
87
76
  wait_thread&.kill
88
77
  end
89
78
  end
90
79
 
91
- attr_reader :context, :payload, :states, :states_by_name, :start_at, :name, :comment
80
+ attr_reader :comment, :context
92
81
 
93
82
  def initialize(payload, context = nil, credentials = nil, name = nil)
94
83
  payload = JSON.parse(payload) if payload.kind_of?(String)
@@ -99,20 +88,10 @@ module Floe
99
88
  # caller should really put credentials into context and not pass that variable
100
89
  context.credentials = credentials if credentials
101
90
 
102
- # NOTE: this is a string, and states use an array
103
- @name = name || "State Machine"
104
- @payload = payload
105
- @context = context
106
- @comment = payload["Comment"]
107
- @start_at = payload["StartAt"]
108
-
109
- # NOTE: Everywhere else we include our name (i.e.: parent name) when building the child name.
110
- # When creating the states, we are dropping our name (i.e.: the workflow name)
111
- @states = payload["States"].to_a.map { |state_name, state| State.build!(self, ["States", state_name], state) }
91
+ @context = context
92
+ @comment = payload["Comment"]
112
93
 
113
- validate_workflow
114
-
115
- @states_by_name = @states.each_with_object({}) { |state, result| result[state.short_name] = state }
94
+ super(payload, name)
116
95
  rescue Floe::Error
117
96
  raise
118
97
  rescue => err
@@ -185,7 +164,7 @@ module Floe
185
164
 
186
165
  # NOTE: Expecting the context to be initialized (via start_workflow) before this
187
166
  def current_state
188
- @states_by_name[context.state_name]
167
+ states_by_name[context.state_name]
189
168
  end
190
169
 
191
170
  # backwards compatibility. Caller should access directly from context
@@ -193,14 +172,12 @@ module Floe
193
172
  @context.credentials
194
173
  end
195
174
 
196
- private
197
-
198
- def validate_workflow
199
- missing_field_error!("States") if @states.empty?
200
- missing_field_error!("StartAt") if @start_at.nil?
201
- invalid_field_error!("StartAt", @start_at, "is not found in \"States\"") unless workflow_state?(@start_at, self)
175
+ def execution_id
176
+ @context.execution["Id"]
202
177
  end
203
178
 
179
+ private
180
+
204
181
  def step!
205
182
  next_state = {"Name" => context.next_state, "Guid" => SecureRandom.uuid, "PreviousStateGuid" => context.state["Guid"]}
206
183
 
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Floe
4
+ class WorkflowBase
5
+ include ValidationMixin
6
+
7
+ attr_reader :name, :payload, :start_at, :states, :states_by_name
8
+
9
+ def initialize(payload, name = nil)
10
+ # NOTE: this is a string, and states use an array
11
+ @name = name || "State Machine"
12
+ @payload = payload
13
+ @start_at = payload["StartAt"]
14
+
15
+ # NOTE: Everywhere else we include our name (i.e.: parent name) when building the child name.
16
+ # When creating the states, we are dropping our name (i.e.: the workflow name)
17
+ @states = payload["States"].to_a.map { |state_name, state| Floe::Workflow::State.build!(self, ["States", state_name], state) }
18
+ @states_by_name = @states.to_h { |state| [state.short_name, state] }
19
+
20
+ validate_workflow!
21
+ end
22
+
23
+ def run(context)
24
+ run_nonblock(context) until context.ended?
25
+ end
26
+
27
+ def run_nonblock(context)
28
+ start_workflow(context)
29
+ loop while step_nonblock(context) == 0 && !context.ended?
30
+ self
31
+ end
32
+
33
+ def step_nonblock(context)
34
+ return Errno::EPERM if context.ended?
35
+
36
+ result = current_state(context).run_nonblock!(context)
37
+ return result if result != 0
38
+
39
+ context.state_history << context.state
40
+ context.next_state ? step!(context) : end_workflow!(context)
41
+
42
+ result
43
+ end
44
+
45
+ def step_nonblock_ready?(context)
46
+ !context.started? || current_state(context).ready?(context)
47
+ end
48
+
49
+ def waiting?(context)
50
+ current_state(context)&.waiting?(context)
51
+ end
52
+
53
+ def wait_until(context)
54
+ current_state(context)&.wait_until(context)
55
+ end
56
+
57
+ def start_workflow(context)
58
+ return if context.state_name
59
+
60
+ context.state["Name"] = start_at
61
+ context.state["Input"] = context.execution["Input"].dup
62
+
63
+ context.execution["StartTime"] = Time.now.utc.iso8601
64
+
65
+ self
66
+ end
67
+
68
+ def current_state(context)
69
+ states_by_name[context.state_name]
70
+ end
71
+
72
+ def end?(context)
73
+ context.ended?
74
+ end
75
+
76
+ def output(context)
77
+ context.output.to_json if end?(context)
78
+ end
79
+
80
+ private
81
+
82
+ def step!(context)
83
+ next_state = {"Name" => context.next_state}
84
+
85
+ # if rerunning due to an error (and we are using Retry)
86
+ if context.state_name == context.next_state && context.failed? && context.state.key?("Retrier")
87
+ next_state.merge!(context.state.slice("RetryCount", "Input", "Retrier"))
88
+ else
89
+ next_state["Input"] = context.output
90
+ end
91
+
92
+ context.state = next_state
93
+ end
94
+
95
+ # Avoiding State#running? because that is potentially expensive.
96
+ # State#run_nonblock! already called running? via State#ready? and
97
+ # called State#finished -- which is what Context#state_finished? is detecting
98
+ def end_workflow!(context)
99
+ context.execution["EndTime"] = context.state["FinishedTime"]
100
+ end
101
+
102
+ def validate_workflow!
103
+ missing_field_error!("States") if @states.empty?
104
+ missing_field_error!("StartAt") if @start_at.nil?
105
+ invalid_field_error!("StartAt", @start_at, "is not found in \"States\"") unless workflow_state?(@start_at, self)
106
+ end
107
+ end
108
+ end
data/lib/floe.rb CHANGED
@@ -8,8 +8,11 @@ require_relative "floe/logging"
8
8
  require_relative "floe/runner"
9
9
 
10
10
  require_relative "floe/validation_mixin"
11
+ require_relative "floe/workflow_base"
11
12
  require_relative "floe/workflow"
13
+ # mixins used by workflow components
12
14
  require_relative "floe/workflow/error_matcher_mixin"
15
+ require_relative "floe/workflow/branch"
13
16
  require_relative "floe/workflow/catcher"
14
17
  require_relative "floe/workflow/choice_rule"
15
18
  require_relative "floe/workflow/choice_rule/not"
@@ -17,6 +20,7 @@ require_relative "floe/workflow/choice_rule/or"
17
20
  require_relative "floe/workflow/choice_rule/and"
18
21
  require_relative "floe/workflow/choice_rule/data"
19
22
  require_relative "floe/workflow/context"
23
+ require_relative "floe/workflow/item_processor"
20
24
  require_relative "floe/workflow/intrinsic_function"
21
25
  require_relative "floe/workflow/intrinsic_function/parser"
22
26
  require_relative "floe/workflow/intrinsic_function/transformer"
@@ -25,11 +29,14 @@ require_relative "floe/workflow/payload_template"
25
29
  require_relative "floe/workflow/reference_path"
26
30
  require_relative "floe/workflow/retrier"
27
31
  require_relative "floe/workflow/state"
32
+ # mixins used by states
33
+ require_relative "floe/workflow/states/child_workflow_mixin"
34
+ require_relative "floe/workflow/states/input_output_mixin"
35
+ require_relative "floe/workflow/states/non_terminal_mixin"
36
+ require_relative "floe/workflow/states/retry_catch_mixin"
28
37
  require_relative "floe/workflow/states/choice"
29
38
  require_relative "floe/workflow/states/fail"
30
- require_relative "floe/workflow/states/input_output_mixin"
31
39
  require_relative "floe/workflow/states/map"
32
- require_relative "floe/workflow/states/non_terminal_mixin"
33
40
  require_relative "floe/workflow/states/parallel"
34
41
  require_relative "floe/workflow/states/pass"
35
42
  require_relative "floe/workflow/states/succeed"