flows 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (147) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/{build.yml → test.yml} +5 -10
  3. data/.gitignore +1 -0
  4. data/.reek.yml +42 -0
  5. data/.rubocop.yml +20 -7
  6. data/.ruby-version +1 -1
  7. data/.yardopts +1 -0
  8. data/CHANGELOG.md +42 -0
  9. data/Gemfile +0 -6
  10. data/Gemfile.lock +139 -74
  11. data/README.md +158 -364
  12. data/Rakefile +35 -1
  13. data/bin/.rubocop.yml +5 -0
  14. data/bin/all_the_errors +47 -0
  15. data/bin/benchmark +73 -105
  16. data/bin/benchmark_cli/compare.rb +118 -0
  17. data/bin/benchmark_cli/compare/a_plus_b.rb +22 -0
  18. data/bin/benchmark_cli/compare/base.rb +45 -0
  19. data/bin/benchmark_cli/compare/command.rb +47 -0
  20. data/bin/benchmark_cli/compare/ten_steps.rb +22 -0
  21. data/bin/benchmark_cli/examples.rb +23 -0
  22. data/bin/benchmark_cli/examples/.rubocop.yml +19 -0
  23. data/bin/benchmark_cli/examples/a_plus_b/dry_do.rb +23 -0
  24. data/bin/benchmark_cli/examples/a_plus_b/dry_transaction.rb +17 -0
  25. data/bin/benchmark_cli/examples/a_plus_b/flows_do.rb +22 -0
  26. data/bin/benchmark_cli/examples/a_plus_b/flows_railway.rb +13 -0
  27. data/bin/benchmark_cli/examples/a_plus_b/flows_scp.rb +13 -0
  28. data/bin/benchmark_cli/examples/a_plus_b/flows_scp_mut.rb +13 -0
  29. data/bin/benchmark_cli/examples/a_plus_b/flows_scp_oc.rb +21 -0
  30. data/bin/benchmark_cli/examples/a_plus_b/trailblazer.rb +15 -0
  31. data/bin/benchmark_cli/examples/ten_steps/dry_do.rb +70 -0
  32. data/bin/benchmark_cli/examples/ten_steps/dry_transaction.rb +64 -0
  33. data/bin/benchmark_cli/examples/ten_steps/flows_do.rb +69 -0
  34. data/bin/benchmark_cli/examples/ten_steps/flows_railway.rb +58 -0
  35. data/bin/benchmark_cli/examples/ten_steps/flows_scp.rb +58 -0
  36. data/bin/benchmark_cli/examples/ten_steps/flows_scp_mut.rb +58 -0
  37. data/bin/benchmark_cli/examples/ten_steps/flows_scp_oc.rb +66 -0
  38. data/bin/benchmark_cli/examples/ten_steps/trailblazer.rb +60 -0
  39. data/bin/benchmark_cli/helpers.rb +12 -0
  40. data/bin/benchmark_cli/ruby.rb +15 -0
  41. data/bin/benchmark_cli/ruby/command.rb +38 -0
  42. data/bin/benchmark_cli/ruby/method_exec.rb +71 -0
  43. data/bin/benchmark_cli/ruby/self_class.rb +69 -0
  44. data/bin/benchmark_cli/ruby/structs.rb +90 -0
  45. data/bin/console +1 -0
  46. data/bin/docserver +7 -0
  47. data/bin/errors +118 -0
  48. data/bin/errors_cli/contract_error_demo.rb +49 -0
  49. data/bin/errors_cli/di_error_demo.rb +38 -0
  50. data/bin/errors_cli/flows_router_error_demo.rb +15 -0
  51. data/bin/errors_cli/oc_error_demo.rb +40 -0
  52. data/bin/errors_cli/railway_error_demo.rb +10 -0
  53. data/bin/errors_cli/result_error_demo.rb +13 -0
  54. data/bin/errors_cli/scp_error_demo.rb +17 -0
  55. data/docs/README.md +2 -186
  56. data/docs/_sidebar.md +0 -24
  57. data/docs/index.html +1 -1
  58. data/flows.gemspec +25 -2
  59. data/forspell.dict +9 -0
  60. data/lefthook.yml +9 -0
  61. data/lib/flows.rb +11 -5
  62. data/lib/flows/contract.rb +402 -0
  63. data/lib/flows/contract/array.rb +55 -0
  64. data/lib/flows/contract/case_eq.rb +41 -0
  65. data/lib/flows/contract/compose.rb +77 -0
  66. data/lib/flows/contract/either.rb +53 -0
  67. data/lib/flows/contract/error.rb +25 -0
  68. data/lib/flows/contract/hash.rb +75 -0
  69. data/lib/flows/contract/hash_of.rb +70 -0
  70. data/lib/flows/contract/helpers.rb +22 -0
  71. data/lib/flows/contract/predicate.rb +34 -0
  72. data/lib/flows/contract/transformer.rb +50 -0
  73. data/lib/flows/contract/tuple.rb +70 -0
  74. data/lib/flows/flow.rb +75 -7
  75. data/lib/flows/flow/node.rb +131 -0
  76. data/lib/flows/flow/router.rb +25 -0
  77. data/lib/flows/flow/router/custom.rb +54 -0
  78. data/lib/flows/flow/router/errors.rb +11 -0
  79. data/lib/flows/flow/router/simple.rb +20 -0
  80. data/lib/flows/plugin.rb +13 -0
  81. data/lib/flows/plugin/dependency_injector.rb +159 -0
  82. data/lib/flows/plugin/dependency_injector/dependency.rb +24 -0
  83. data/lib/flows/plugin/dependency_injector/dependency_definition.rb +16 -0
  84. data/lib/flows/plugin/dependency_injector/dependency_list.rb +57 -0
  85. data/lib/flows/plugin/dependency_injector/errors.rb +58 -0
  86. data/lib/flows/plugin/implicit_init.rb +45 -0
  87. data/lib/flows/plugin/output_contract.rb +84 -0
  88. data/lib/flows/plugin/output_contract/dsl.rb +36 -0
  89. data/lib/flows/plugin/output_contract/errors.rb +74 -0
  90. data/lib/flows/plugin/output_contract/wrapper.rb +53 -0
  91. data/lib/flows/railway.rb +140 -37
  92. data/lib/flows/railway/dsl.rb +8 -19
  93. data/lib/flows/railway/errors.rb +8 -12
  94. data/lib/flows/railway/step.rb +24 -0
  95. data/lib/flows/railway/step_list.rb +38 -0
  96. data/lib/flows/result.rb +188 -2
  97. data/lib/flows/result/do.rb +160 -16
  98. data/lib/flows/result/err.rb +12 -6
  99. data/lib/flows/result/errors.rb +29 -17
  100. data/lib/flows/result/helpers.rb +25 -3
  101. data/lib/flows/result/ok.rb +12 -6
  102. data/lib/flows/shared_context_pipeline.rb +216 -0
  103. data/lib/flows/shared_context_pipeline/dsl.rb +63 -0
  104. data/lib/flows/shared_context_pipeline/errors.rb +17 -0
  105. data/lib/flows/shared_context_pipeline/mutation_step.rb +31 -0
  106. data/lib/flows/shared_context_pipeline/router_definition.rb +21 -0
  107. data/lib/flows/shared_context_pipeline/step.rb +46 -0
  108. data/lib/flows/shared_context_pipeline/track.rb +67 -0
  109. data/lib/flows/shared_context_pipeline/track_list.rb +46 -0
  110. data/lib/flows/util.rb +17 -0
  111. data/lib/flows/util/inheritable_singleton_vars.rb +79 -0
  112. data/lib/flows/util/inheritable_singleton_vars/dup_strategy.rb +109 -0
  113. data/lib/flows/util/inheritable_singleton_vars/isolation_strategy.rb +104 -0
  114. data/lib/flows/util/prepend_to_class.rb +145 -0
  115. data/lib/flows/version.rb +1 -1
  116. metadata +233 -37
  117. data/bin/demo +0 -66
  118. data/bin/examples.rb +0 -195
  119. data/bin/profile_10steps +0 -106
  120. data/bin/ruby_benchmarks +0 -26
  121. data/docs/CNAME +0 -1
  122. data/docs/contributing/benchmarks_profiling.md +0 -3
  123. data/docs/contributing/local_development.md +0 -3
  124. data/docs/flow/direct_usage.md +0 -3
  125. data/docs/flow/general_idea.md +0 -3
  126. data/docs/operation/basic_usage.md +0 -1
  127. data/docs/operation/inject_steps.md +0 -3
  128. data/docs/operation/lambda_steps.md +0 -3
  129. data/docs/operation/result_shapes.md +0 -3
  130. data/docs/operation/routing_tracks.md +0 -3
  131. data/docs/operation/wrapping_steps.md +0 -3
  132. data/docs/overview/performance.md +0 -336
  133. data/docs/railway/basic_usage.md +0 -232
  134. data/docs/result_objects/basic_usage.md +0 -196
  135. data/docs/result_objects/do_notation.md +0 -139
  136. data/lib/flows/implicit_build.rb +0 -16
  137. data/lib/flows/node.rb +0 -27
  138. data/lib/flows/operation.rb +0 -55
  139. data/lib/flows/operation/builder.rb +0 -130
  140. data/lib/flows/operation/builder/build_router.rb +0 -37
  141. data/lib/flows/operation/dsl.rb +0 -93
  142. data/lib/flows/operation/errors.rb +0 -75
  143. data/lib/flows/operation/executor.rb +0 -78
  144. data/lib/flows/railway/builder.rb +0 -68
  145. data/lib/flows/railway/executor.rb +0 -23
  146. data/lib/flows/result_router.rb +0 -14
  147. data/lib/flows/router.rb +0 -22
@@ -1,29 +1,41 @@
1
1
  module Flows
2
2
  class Result
3
- # Error for unwrapping non-successful result object
4
- class UnwrapError < Flows::Error
5
- def initialize(status, data, meta)
6
- @status = status
7
- @data = data
8
- @meta = meta
3
+ # Base class for Result errors.
4
+ class Error < ::Flows::Error; end
5
+
6
+ # Error for invalid data access cases
7
+ class AccessError < Flows::Error
8
+ def initialize(result)
9
+ @result = result
9
10
  end
10
11
 
11
12
  def message
12
- "You're trying to unwrap non-successful result with status `#{@status.inspect}` and data `#{@data.inspect}`\n\
13
- Result metadata: `#{@meta.inspect}`"
13
+ [
14
+ base_msg,
15
+ " Result status: `#{@result.status.inspect}`",
16
+ " Result data: `#{data.inspect}`",
17
+ " Result meta: `#{@result.meta.inspect}`"
18
+ ].join("\n")
14
19
  end
15
- end
16
20
 
17
- # Error for dealing with failure result as success one
18
- class NoErrorError < Flows::Error
19
- def initialize(status, data)
20
- @status = status
21
- @data = data
21
+ private
22
+
23
+ def base_msg
24
+ case @result
25
+ when Flows::Result::Ok
26
+ 'Data in a successful result must be retrieved using `#unwrap` method, not `#error`.'
27
+ when Flows::Result::Err
28
+ 'Data in a failure result must be retrieved using `#error` method, not `#unwrap`.'
29
+ end
22
30
  end
23
31
 
24
- def message
25
- "You're trying to get error data for successful result with status \
26
- `#{@status.inspect}` and data `#{@data.inspect}`"
32
+ def data
33
+ case @result
34
+ when Flows::Result::Ok
35
+ @result.unwrap
36
+ when Flows::Result::Err
37
+ @result.error
38
+ end
27
39
  end
28
40
  end
29
41
  end
@@ -1,14 +1,36 @@
1
1
  module Flows
2
2
  class Result
3
- # Shortcuts for building result objects
3
+ # Shortcuts for building and matching result objects.
4
+ #
5
+ # `:reek:UtilityFunction` and `:reek:FeatureEnvy` checks should be disabled here
6
+ # because this module is intended to contain private utility methods only.
7
+ #
8
+ # This module defines the following private methods:
9
+ #
10
+ # * `ok(status = :ok, **data)` - for building successful results from a hash of keyword arguments.
11
+ # * `ok_data(data, status: :ok)` - for building successful results from any data.
12
+ # * `err(status = :err, **data)` - for building failure results from a hash of keyword arguments.
13
+ # * `err_data(data, status: :err)` - for building failure results from any data.
14
+ # * `match_ok(status = nil)` - for case matching against successful results.
15
+ # * `match_err(status = nil)` - for case matching against failure results.
16
+ #
17
+ # @see Flows::Result usage examples provided here
4
18
  module Helpers
5
19
  private
6
20
 
7
- def ok(status = :success, **data)
21
+ def ok(status = :ok, **data)
8
22
  Flows::Result::Ok.new(data, status: status)
9
23
  end
10
24
 
11
- def err(status = :failure, **data)
25
+ def ok_data(data, status: :ok)
26
+ Flows::Result::Ok.new(data, status: status)
27
+ end
28
+
29
+ def err(status = :err, **data)
30
+ Flows::Result::Err.new(data, status: status)
31
+ end
32
+
33
+ def err_data(data, status: :err)
12
34
  Flows::Result::Err.new(data, status: status)
13
35
  end
14
36
 
@@ -1,25 +1,31 @@
1
1
  module Flows
2
2
  class Result
3
- # Wrapper for successful results
3
+ # Result Object for successful results.
4
+ #
5
+ # @see Flows::Result behaviour described here
4
6
  class Ok < Result
5
- attr_reader :unwrap
6
-
7
- def initialize(data, status: :success, meta: {})
8
- @unwrap = data
7
+ def initialize(data, status: :ok, meta: {})
8
+ @data = data
9
9
  @status = status
10
10
  @meta = meta
11
11
  end
12
12
 
13
+ def unwrap
14
+ @data
15
+ end
16
+
17
+ # @return [true]
13
18
  def ok?
14
19
  true
15
20
  end
16
21
 
22
+ # @return [false]
17
23
  def err?
18
24
  false
19
25
  end
20
26
 
21
27
  def error
22
- raise NoErrorError.new(@status, @data)
28
+ raise AccessError, self
23
29
  end
24
30
  end
25
31
  end
@@ -0,0 +1,216 @@
1
+ require_relative 'shared_context_pipeline/errors'
2
+ require_relative 'shared_context_pipeline/router_definition'
3
+ require_relative 'shared_context_pipeline/step'
4
+ require_relative 'shared_context_pipeline/mutation_step'
5
+ require_relative 'shared_context_pipeline/track'
6
+ require_relative 'shared_context_pipeline/track_list'
7
+ require_relative 'shared_context_pipeline/dsl'
8
+
9
+ module Flows
10
+ # Abstraction for organizing calculations in a shared data context.
11
+ #
12
+ # Let's start with example. Let's say we have to calculate `(a + b) * (a - b)`:
13
+ #
14
+ # class Claculation < Flows::SharedContextPipeline
15
+ # step :calc_left_part
16
+ # step :calc_right_part
17
+ # step :calc_result
18
+ #
19
+ # def calc_left_part(a:, b:, **)
20
+ # ok(left: a + b)
21
+ # end
22
+ #
23
+ # def calc_right_part(a:, b:, **)
24
+ # ok(right: a - b)
25
+ # end
26
+ #
27
+ # def calc_result(left:, right:, **)
28
+ # ok(result: left * right)
29
+ # end
30
+ # end
31
+ #
32
+ # x = Calculation.call(a: 1, b: 2)
33
+ # # x is a `Flows::Result::Ok`
34
+ #
35
+ # x.unwrap
36
+ # # => { a: 1, b: 2, left: 3, right: -1, result: -3 }
37
+ #
38
+ # It works by the following rules:
39
+ #
40
+ # * execution context is a Hash with Symbol keys.
41
+ # * input becomes initial execution context.
42
+ # * steps are executed in a provided order.
43
+ # * actual execution context becomes a step input.
44
+ # * step implementation is a public method with the same name.
45
+ # * step implementation must return {Flows::Result} ({Flows::Result::Helpers} already included).
46
+ # * Result Object data will be merged to shared context after each step execution.
47
+ # * If returned Result Object is successful - next step will be executed,
48
+ # in the case of the last step a calculation will be finished
49
+ # * If returned Result Object is failure - a calculation will be finished
50
+ # * When calculation is finished a Result Object will be returned:
51
+ # * result will have the same type and status as in the last executed step result
52
+ # * result wull have a full execution context as data
53
+ #
54
+ # ## Mutation Steps
55
+ #
56
+ # You may use a different step definition way:
57
+ #
58
+ # class MyClass < Flows::SharedContextPipeline
59
+ # mut_step :hello
60
+ #
61
+ # def hello(ctx)
62
+ # ctx[:result] = 'hello'
63
+ # end
64
+ # end
65
+ #
66
+ # When you use `mut_step` DSL method you define a step with different rules for implementation:
67
+ #
68
+ # * step implementation receives _one_ argument and it's your execution context in a form of a mutable Hash
69
+ # * step implementation can modify execution context
70
+ # * if step implementation returns
71
+ # * "truly" value - it makes step successful with default status `:ok`
72
+ # * "falsey" value - it makes step failure with default status `:err`
73
+ # * {Result} - it works like for standard step, but data is ignored. Only result type and status have effect.
74
+ #
75
+ # ## Tracks & Routes
76
+ #
77
+ # In some situations you may want some branching in a mix. Let's provide an example for a common problem
78
+ # when you have to do some additional steps in case of multiple types of errors.
79
+ # Let's say report to some external system:
80
+ #
81
+ # class SafeFetchComment < Flows::SharedContextPipeline
82
+ # step :fetch_post, routes(
83
+ # match_ok => :next,
84
+ # match_err => :handle_error
85
+ # )
86
+ # step :fetch_comment, routes(
87
+ # match_ok => :next,
88
+ # match_err => :handle_error
89
+ # )
90
+ #
91
+ # track :handle_error do
92
+ # step :report_to_external_system
93
+ # step :write_logs
94
+ # end
95
+ #
96
+ # # steps implementations here
97
+ # end
98
+ #
99
+ # Let's describe how `routes(...)` and `track` DSL methods work.
100
+ #
101
+ # Each step has a router. Router is defined by a hash and `router(...)` method itself is
102
+ # a (almost) shortcut to {Flows::Flow::Router::Custom} constructor. By default each step has the following
103
+ # router definition:
104
+ #
105
+ # {
106
+ # match_ok => :next, # next step is calculated by DSL, this symbol will be substituted in a final router
107
+ # match_err => :end # `:end` means stop execution
108
+ # }
109
+ #
110
+ # Hash provided in `router(...)` method will override default hash to make a final router.
111
+ #
112
+ # Because of symbols with special behavior (`:end`, `:next`) you cannot name your steps or tracks
113
+ # `:next` or `:end`. And this is totally ok because it is reserved words in Ruby.
114
+ #
115
+ # By the way, you can route not only to tracks, but also to steps. And by using `match_ok(status)` and
116
+ # `match_err(status)` you can have different routes for different statuses of successful or failure results.
117
+ #
118
+ # Steps defined inside a track are fully isolated. The simple way is to think about track as a totally
119
+ # separate pipeline. You have to explicitly enter to it. And explicitly return from it to root-level steps
120
+ # if you want to continue execution.
121
+ #
122
+ # If you feel it's too much verbose to route many steps to the same track you can do something like this:
123
+ #
124
+ # class SafeFetchComment < Flows::SharedContextPipeline
125
+ # def self.safe_step(name)
126
+ # step name, routes(
127
+ # match_ok => :next,
128
+ # match_err => :handle_error
129
+ # )
130
+ # end
131
+ #
132
+ # safe_step :fetch_post
133
+ # safe_step :fetch_comment
134
+ #
135
+ # track :handle_error do
136
+ # step :report_to_external_system
137
+ # step :write_logs
138
+ # end
139
+ #
140
+ # # steps implementations here
141
+ # end
142
+ #
143
+ # ## Callbacks
144
+ #
145
+ # You may want to have some logic to execute before all steps, or after all, or before each, or after each.
146
+ # For example to inject generalized execution process logging.
147
+ # To achieve this you can use callbacks:
148
+ #
149
+ # class MySCP < Flows::SharedContextPipeline
150
+ # before_all do |klass, context|
151
+ # # you can modify execution context here
152
+ # # return value will be ignored
153
+ # end
154
+ #
155
+ # after_all do |klass, pipeline_result|
156
+ # # you must provide final result object for pipeline here
157
+ # # if no modifications needed - just return provided pipeline_result
158
+ # end
159
+ #
160
+ # before_each do |klass, step_name, context|
161
+ # # you can modify context here
162
+ # # return value will be ignored
163
+ # end
164
+ #
165
+ # after_each do |klass, step_name, context, step_result|
166
+ # # you can modify context here
167
+ # # you must not modify step_result
168
+ # # context already has data from step_result at the moment of execution
169
+ # # return value will be ignored
170
+ # end
171
+ # end
172
+ class SharedContextPipeline
173
+ extend ::Flows::Plugin::ImplicitInit
174
+
175
+ include ::Flows::Result::Helpers
176
+ extend ::Flows::Result::Helpers
177
+
178
+ extend DSL
179
+
180
+ def initialize
181
+ klass = self.class
182
+ tracks = klass.tracks
183
+
184
+ raise NoStepsError, klass if tracks.main_track_empty?
185
+
186
+ @__flow = Flows::Flow.new(
187
+ start_node: tracks.first_step_name,
188
+ node_map: tracks.to_node_map(self)
189
+ )
190
+ end
191
+
192
+ # Executes pipeline with provided keyword arguments, returns Result Object.
193
+ #
194
+ # @return [Flows::Result]
195
+ def call(**kwargs) # rubocop:disable Metrics/MethodLength
196
+ klass = self.class
197
+ context = { data: kwargs, class: klass }
198
+
199
+ klass.before_all_callbacks.each do |callback|
200
+ callback.call(klass, context[:data])
201
+ end
202
+
203
+ flow_result = @__flow.call(nil, context: context)
204
+
205
+ final_result = flow_result.class.new(
206
+ context[:data],
207
+ status: flow_result.status,
208
+ meta: { last_step: context[:last_step] }
209
+ )
210
+
211
+ klass.after_all_callbacks.reduce(final_result) do |result, callback|
212
+ callback.call(klass, result)
213
+ end
214
+ end
215
+ end
216
+ end
@@ -0,0 +1,63 @@
1
+ module Flows
2
+ class SharedContextPipeline
3
+ # @api private
4
+ module DSL
5
+ attr_reader :tracks
6
+ attr_reader :before_all_callbacks
7
+ attr_reader :after_all_callbacks
8
+ attr_reader :before_each_callbacks
9
+ attr_reader :after_each_callbacks
10
+
11
+ DEFAULT_ROUTER_DEF = RouterDefinition.new(
12
+ Flows::Result::Ok => :next,
13
+ Flows::Result::Err => :end
14
+ )
15
+
16
+ Flows::Util::InheritableSingletonVars::DupStrategy.call(
17
+ self,
18
+ '@tracks' => TrackList.new,
19
+ '@before_all_callbacks' => [],
20
+ '@after_all_callbacks' => [],
21
+ '@before_each_callbacks' => [],
22
+ '@after_each_callbacks' => []
23
+ )
24
+
25
+ def step(name, router_def = DEFAULT_ROUTER_DEF, &lambda)
26
+ tracks.add_step(name: name, lambda: lambda, router_def: router_def)
27
+ end
28
+
29
+ def mut_step(name, router_def = DEFAULT_ROUTER_DEF, &lambda)
30
+ tracks.add_mutation_step(name: name, lambda: lambda, router_def: router_def)
31
+ end
32
+
33
+ def track(name, &block)
34
+ track_before = tracks.current_track
35
+
36
+ tracks.switch_track(name)
37
+ instance_exec(&block)
38
+ tracks.switch_track(track_before)
39
+ end
40
+
41
+ # :reek:UtilityFunction is allowed here
42
+ def routes(routes_def)
43
+ RouterDefinition.new(routes_def)
44
+ end
45
+
46
+ def before_all(&callback)
47
+ before_all_callbacks << callback
48
+ end
49
+
50
+ def after_all(&callback)
51
+ after_all_callbacks << callback
52
+ end
53
+
54
+ def before_each(&callback)
55
+ before_each_callbacks << callback
56
+ end
57
+
58
+ def after_each(&callback)
59
+ after_each_callbacks << callback
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,17 @@
1
+ module Flows
2
+ class SharedContextPipeline
3
+ # Base error class for {SharedContextPipeline} errors.
4
+ class Error < StandardError; end
5
+
6
+ # Raised when initializing {SharedContextPipeline} with no steps.
7
+ class NoStepsError < Error
8
+ def initialize(klass)
9
+ @klass = klass
10
+ end
11
+
12
+ def message
13
+ "No steps defined for main track in #{@klass}"
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,31 @@
1
+ module Flows
2
+ class SharedContextPipeline
3
+ EMPTY_HASH = {}.freeze
4
+ EMPTY_OK = Flows::Result::Ok.new(nil).freeze
5
+ EMPTY_ERR = Flows::Result::Err.new(nil).freeze
6
+
7
+ # @api private
8
+ class MutationStep < Step
9
+ NODE_PREPROCESSOR = lambda do |_input, context, meta|
10
+ context[:last_step] = meta[:name]
11
+
12
+ context[:class].before_each_callbacks.each do |callback|
13
+ callback.call(context[:class], meta[:name], context[:data])
14
+ end
15
+
16
+ [[context[:data]], EMPTY_HASH]
17
+ end
18
+
19
+ NODE_POSTPROCESSOR = lambda do |output, context, meta|
20
+ case output
21
+ when Flows::Result then output
22
+ else output ? EMPTY_OK : EMPTY_ERR
23
+ end.tap do |result|
24
+ context[:class].after_each_callbacks.each do |callback|
25
+ callback.call(context[:class], meta[:name], context[:data], result)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end