flows 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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