flows 0.2.0 → 0.6.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 (166) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/{build.yml → test.yml} +5 -10
  3. data/.gitignore +9 -1
  4. data/.mdlrc +1 -1
  5. data/.reek.yml +54 -0
  6. data/.rubocop.yml +26 -7
  7. data/.rubocop_todo.yml +27 -0
  8. data/.ruby-version +1 -1
  9. data/.yardopts +1 -0
  10. data/CHANGELOG.md +81 -0
  11. data/Gemfile +0 -6
  12. data/README.md +167 -363
  13. data/Rakefile +35 -1
  14. data/bin/.rubocop.yml +5 -0
  15. data/bin/all_the_errors +55 -0
  16. data/bin/benchmark +73 -105
  17. data/bin/benchmark_cli/compare.rb +118 -0
  18. data/bin/benchmark_cli/compare/a_plus_b.rb +22 -0
  19. data/bin/benchmark_cli/compare/base.rb +45 -0
  20. data/bin/benchmark_cli/compare/command.rb +47 -0
  21. data/bin/benchmark_cli/compare/ten_steps.rb +22 -0
  22. data/bin/benchmark_cli/examples.rb +23 -0
  23. data/bin/benchmark_cli/examples/.rubocop.yml +22 -0
  24. data/bin/benchmark_cli/examples/a_plus_b/dry_do.rb +23 -0
  25. data/bin/benchmark_cli/examples/a_plus_b/dry_transaction.rb +17 -0
  26. data/bin/benchmark_cli/examples/a_plus_b/flows_do.rb +22 -0
  27. data/bin/benchmark_cli/examples/a_plus_b/flows_railway.rb +13 -0
  28. data/bin/benchmark_cli/examples/a_plus_b/flows_scp.rb +13 -0
  29. data/bin/benchmark_cli/examples/a_plus_b/flows_scp_mut.rb +13 -0
  30. data/bin/benchmark_cli/examples/a_plus_b/flows_scp_oc.rb +21 -0
  31. data/bin/benchmark_cli/examples/a_plus_b/trailblazer.rb +15 -0
  32. data/bin/benchmark_cli/examples/ten_steps/dry_do.rb +70 -0
  33. data/bin/benchmark_cli/examples/ten_steps/dry_transaction.rb +64 -0
  34. data/bin/benchmark_cli/examples/ten_steps/flows_do.rb +69 -0
  35. data/bin/benchmark_cli/examples/ten_steps/flows_railway.rb +58 -0
  36. data/bin/benchmark_cli/examples/ten_steps/flows_scp.rb +58 -0
  37. data/bin/benchmark_cli/examples/ten_steps/flows_scp_mut.rb +58 -0
  38. data/bin/benchmark_cli/examples/ten_steps/flows_scp_oc.rb +66 -0
  39. data/bin/benchmark_cli/examples/ten_steps/trailblazer.rb +60 -0
  40. data/bin/benchmark_cli/helpers.rb +12 -0
  41. data/bin/benchmark_cli/ruby.rb +15 -0
  42. data/bin/benchmark_cli/ruby/command.rb +38 -0
  43. data/bin/benchmark_cli/ruby/method_exec.rb +71 -0
  44. data/bin/benchmark_cli/ruby/self_class.rb +69 -0
  45. data/bin/benchmark_cli/ruby/structs.rb +90 -0
  46. data/bin/console +1 -0
  47. data/bin/docserver +7 -0
  48. data/bin/errors +138 -0
  49. data/bin/errors_cli/contract_error_demo.rb +49 -0
  50. data/bin/errors_cli/di_error_demo.rb +38 -0
  51. data/bin/errors_cli/flow_error_demo.rb +22 -0
  52. data/bin/errors_cli/flows_router_error_demo.rb +15 -0
  53. data/bin/errors_cli/interface_error_demo.rb +17 -0
  54. data/bin/errors_cli/oc_error_demo.rb +40 -0
  55. data/bin/errors_cli/railway_error_demo.rb +10 -0
  56. data/bin/errors_cli/result_error_demo.rb +13 -0
  57. data/bin/errors_cli/scp_error_demo.rb +17 -0
  58. data/docs/README.md +3 -187
  59. data/docs/_sidebar.md +0 -24
  60. data/docs/index.html +1 -1
  61. data/flows.gemspec +27 -2
  62. data/forspell.dict +9 -0
  63. data/lefthook.yml +9 -0
  64. data/lib/flows.rb +11 -5
  65. data/lib/flows/contract.rb +402 -0
  66. data/lib/flows/contract/array.rb +55 -0
  67. data/lib/flows/contract/case_eq.rb +43 -0
  68. data/lib/flows/contract/compose.rb +77 -0
  69. data/lib/flows/contract/either.rb +53 -0
  70. data/lib/flows/contract/error.rb +24 -0
  71. data/lib/flows/contract/hash.rb +75 -0
  72. data/lib/flows/contract/hash_of.rb +70 -0
  73. data/lib/flows/contract/helpers.rb +22 -0
  74. data/lib/flows/contract/predicate.rb +34 -0
  75. data/lib/flows/contract/transformer.rb +50 -0
  76. data/lib/flows/contract/tuple.rb +70 -0
  77. data/lib/flows/flow.rb +96 -7
  78. data/lib/flows/flow/errors.rb +29 -0
  79. data/lib/flows/flow/node.rb +132 -0
  80. data/lib/flows/flow/router.rb +29 -0
  81. data/lib/flows/flow/router/custom.rb +59 -0
  82. data/lib/flows/flow/router/errors.rb +11 -0
  83. data/lib/flows/flow/router/simple.rb +25 -0
  84. data/lib/flows/plugin.rb +15 -0
  85. data/lib/flows/plugin/dependency_injector.rb +170 -0
  86. data/lib/flows/plugin/dependency_injector/dependency.rb +24 -0
  87. data/lib/flows/plugin/dependency_injector/dependency_definition.rb +16 -0
  88. data/lib/flows/plugin/dependency_injector/dependency_list.rb +55 -0
  89. data/lib/flows/plugin/dependency_injector/errors.rb +58 -0
  90. data/lib/flows/plugin/implicit_init.rb +45 -0
  91. data/lib/flows/plugin/interface.rb +84 -0
  92. data/lib/flows/plugin/output_contract.rb +85 -0
  93. data/lib/flows/plugin/output_contract/dsl.rb +48 -0
  94. data/lib/flows/plugin/output_contract/errors.rb +74 -0
  95. data/lib/flows/plugin/output_contract/wrapper.rb +55 -0
  96. data/lib/flows/plugin/profiler.rb +114 -0
  97. data/lib/flows/plugin/profiler/injector.rb +35 -0
  98. data/lib/flows/plugin/profiler/report.rb +48 -0
  99. data/lib/flows/plugin/profiler/report/events.rb +43 -0
  100. data/lib/flows/plugin/profiler/report/flat.rb +41 -0
  101. data/lib/flows/plugin/profiler/report/flat/method_report.rb +80 -0
  102. data/lib/flows/plugin/profiler/report/raw.rb +15 -0
  103. data/lib/flows/plugin/profiler/report/tree.rb +98 -0
  104. data/lib/flows/plugin/profiler/report/tree/calculated_node.rb +116 -0
  105. data/lib/flows/plugin/profiler/report/tree/node.rb +34 -0
  106. data/lib/flows/plugin/profiler/wrapper.rb +53 -0
  107. data/lib/flows/railway.rb +140 -34
  108. data/lib/flows/railway/dsl.rb +8 -18
  109. data/lib/flows/railway/errors.rb +8 -12
  110. data/lib/flows/railway/step.rb +24 -0
  111. data/lib/flows/railway/step_list.rb +38 -0
  112. data/lib/flows/result.rb +188 -2
  113. data/lib/flows/result/do.rb +158 -16
  114. data/lib/flows/result/err.rb +12 -6
  115. data/lib/flows/result/errors.rb +29 -17
  116. data/lib/flows/result/helpers.rb +25 -3
  117. data/lib/flows/result/ok.rb +12 -6
  118. data/lib/flows/shared_context_pipeline.rb +342 -0
  119. data/lib/flows/shared_context_pipeline/dsl.rb +12 -0
  120. data/lib/flows/shared_context_pipeline/dsl/callbacks.rb +35 -0
  121. data/lib/flows/shared_context_pipeline/dsl/tracks.rb +52 -0
  122. data/lib/flows/shared_context_pipeline/errors.rb +17 -0
  123. data/lib/flows/shared_context_pipeline/mutation_step.rb +30 -0
  124. data/lib/flows/shared_context_pipeline/router_definition.rb +21 -0
  125. data/lib/flows/shared_context_pipeline/step.rb +55 -0
  126. data/lib/flows/shared_context_pipeline/track.rb +54 -0
  127. data/lib/flows/shared_context_pipeline/track_list.rb +51 -0
  128. data/lib/flows/shared_context_pipeline/wrap.rb +73 -0
  129. data/lib/flows/util.rb +17 -0
  130. data/lib/flows/util/inheritable_singleton_vars.rb +86 -0
  131. data/lib/flows/util/inheritable_singleton_vars/dup_strategy.rb +100 -0
  132. data/lib/flows/util/inheritable_singleton_vars/isolation_strategy.rb +91 -0
  133. data/lib/flows/util/prepend_to_class.rb +191 -0
  134. data/lib/flows/version.rb +1 -1
  135. metadata +253 -38
  136. data/Gemfile.lock +0 -174
  137. data/bin/demo +0 -66
  138. data/bin/examples.rb +0 -195
  139. data/bin/profile_10steps +0 -106
  140. data/bin/ruby_benchmarks +0 -26
  141. data/docs/CNAME +0 -1
  142. data/docs/contributing/benchmarks_profiling.md +0 -3
  143. data/docs/contributing/local_development.md +0 -3
  144. data/docs/flow/direct_usage.md +0 -3
  145. data/docs/flow/general_idea.md +0 -3
  146. data/docs/operation/basic_usage.md +0 -1
  147. data/docs/operation/inject_steps.md +0 -3
  148. data/docs/operation/lambda_steps.md +0 -3
  149. data/docs/operation/result_shapes.md +0 -3
  150. data/docs/operation/routing_tracks.md +0 -3
  151. data/docs/operation/wrapping_steps.md +0 -3
  152. data/docs/overview/performance.md +0 -336
  153. data/docs/railway/basic_usage.md +0 -232
  154. data/docs/result_objects/basic_usage.md +0 -196
  155. data/docs/result_objects/do_notation.md +0 -139
  156. data/lib/flows/node.rb +0 -27
  157. data/lib/flows/operation.rb +0 -52
  158. data/lib/flows/operation/builder.rb +0 -130
  159. data/lib/flows/operation/builder/build_router.rb +0 -37
  160. data/lib/flows/operation/dsl.rb +0 -93
  161. data/lib/flows/operation/errors.rb +0 -75
  162. data/lib/flows/operation/executor.rb +0 -78
  163. data/lib/flows/railway/builder.rb +0 -68
  164. data/lib/flows/railway/executor.rb +0 -23
  165. data/lib/flows/result_router.rb +0 -14
  166. data/lib/flows/router.rb +0 -22
@@ -1,196 +0,0 @@
1
- # Result Object :: Basic Usage
2
-
3
- Result Object is a way of presenting the result of a calculation. The result may be successful or failed.
4
- For example, if you calculate expression `a / b`:
5
-
6
- * for `a = 6` and `b = 2` result will be successful with data `3`.
7
- * for `a = 6` and `b = 0` result will be failed with data, for example, `"Cannot divide by zero"`.
8
-
9
- Examples of such approach may be found in other libraries and languages:
10
-
11
- * [Either Monad](https://hackage.haskell.org/package/category-extras-0.52.0/docs/Control-Monad-Either.html) in Haskell
12
- * [Result Type](https://doc.rust-lang.org/std/result/enum.Result.html) in Rust
13
- * [Faraday gem](https://www.rubydoc.info/gems/faraday/Faraday/Response) has `Faraday::Response` object which contains data and status
14
- * [dry-rb Result Monad](https://dry-rb.org/gems/dry-monads/result/) has `Dry::Monads::Result`
15
-
16
- So, why do you need Result Object? Why not just return `nil` on a failure or raise an error (like in the standard library)? Here are several reasons:
17
-
18
- * raising errors and exceptions isn't a very convenient and explicit way to handle errors. Moreover, it is slow and looks like `goto`. However, it is still a good way to abort execution on an unexpected error.
19
- * returning `nil` does not work when you have to deal with different types of errors or an error has some data payload.
20
- * using specific Result Objects (like `Faraday::Response`) brings inconsistency - you have to learn how to deal with each new type of Result.
21
-
22
- That's why `Flows` should have Result Object implementation. If any executable Flows entity will return Result Object with the same API - composing your app components becomes trivial. Result Objects should also be as fast and lightweight as possible.
23
-
24
- Flows' implementation is inspired mainly by [Rust Result Type](https://doc.rust-lang.org/std/result/enum.Result.html) and focused on following features:
25
-
26
- * use idiomatic Ruby: no methods named with first capital letter (`Name(1, 2)`), etc.
27
- * provide convenient helpers for `case` and `===` (case equality) for matching results and writing routing logic
28
- * provide helpers for convenient creation of Result Objects
29
- * Result Object may be successful (`Ok`) or failure (`Err`)
30
- * Result Object has an status (some symbol: `:saved`, `:zero_division_error`)
31
- * status usage is optional. Default statuses for successful and failure results are `:success` and `:failure`
32
- * result may have metadata. Metadata is something unrelated to your business logic (execution time, for example, or some info about who created this result).
33
- * different accessors for successful and failure results - prevents treating failure results as successful and vice versa.
34
-
35
- ## Class Diagram
36
-
37
- Class UML diagram describing current implementation:
38
-
39
- ```plantuml
40
- @startuml
41
- class Flows::Result<Abstract Class> {
42
- .. Constructor ..
43
- {static} new(Symbol status, Hash data, Hash metadata)
44
- .. Success checks ..
45
- {abstract} bool ok?()
46
- {abstract} bool err?()
47
- .. Result data access ..
48
- Symbol status()
49
- {abstract} Hash unwrap()
50
- {abstract} Hash error()
51
- .. Metadata ..
52
- Hash meta()
53
- }
54
-
55
- class Flows::Result::Ok {
56
- true ok?()
57
- false err?()
58
- ..
59
- Hash unwrap()
60
- [raise exception] error()
61
- }
62
-
63
- class Flows::Result::Err {
64
- false ok?()
65
- true err?()
66
- ..
67
- [raise exception] unwrap()
68
- Hash error()
69
- }
70
-
71
- Flows::Result --> Flows::Result::Ok
72
- Flows::Result --> Flows::Result::Err
73
- @enduml
74
- ```
75
-
76
- ## Creating Results
77
-
78
- Most flexible and verbose way of creating Result Objects is creating via `.new`:
79
-
80
- ```ruby
81
- # Successful result with data {a: 1}
82
- Flows::Result::Ok.new(a: 1)
83
-
84
- # Failure result with data {msg: 'error'}
85
- Flows::Result::Err.new(msg: 'error')
86
-
87
- # Successful result with data {a: 1} and status `:done`
88
- Flows::Result::Ok.new({ a: 1 }, status: :done)
89
-
90
- # Failure result with data {msg: 'error'} and status `:http_error`
91
- Flows::Result::Err.new({ msg: 'error' }, status: :http_error)
92
-
93
- # Successful result with data {a: 1} and metadata `{ time: 123 }`
94
- Flows::Result::Ok.new({ a: 1 }, meta: { time: 123 })
95
-
96
- # Failure result with data {msg: 'error'} and metadata `{ time: 123 }`
97
- Flows::Result::Err.new({ msg: 'error' }, meta: { time: 123 })
98
- ```
99
-
100
- More convenient and short way is to use helpers:
101
-
102
- ```ruby
103
- include Flows::Result::Helpers
104
-
105
- # Successful result with data {a: 1}
106
- ok(a: 1)
107
-
108
- # Failure result with data {msg: 'error'}
109
- err(msg: 'error')
110
-
111
- # Successful result with data {a: 1} and status `:done`
112
- ok(:done, a: 1)
113
-
114
- # Failure result with data {msg: 'error'} and status `:http_error`
115
- err(:http_error, msg: 'error')
116
- ```
117
-
118
- You cannot provide metadata using helpers and it's ok: you shouldn't populate metadata in your business code.
119
- Metadata is designed to use in library code and when you have to provide some metadata from your library - just use `.new` instead of helpers.
120
-
121
- ## Inspecting Results
122
-
123
- Behaviour of any result object:
124
-
125
- ```ruby
126
- result.status # returns status, example: `:success`
127
-
128
- result.meta # returns metadata, example: `{}`
129
- ```
130
-
131
- Behaviour specific to successful results:
132
-
133
- ```ruby
134
- result.ok? # true
135
-
136
- result.err? # false
137
-
138
- result.unwrap # returns result data
139
-
140
- result.error # raises exception
141
- ```
142
-
143
- Behaviour specific to failure results:
144
-
145
- ```ruby
146
- result.ok? # false
147
-
148
- result.err? # true
149
-
150
- result.unwrap # raises exception
151
-
152
- result.error # returns result data
153
- ```
154
-
155
- ## Matching Results
156
-
157
- Basic matching results using `case`:
158
-
159
- ```ruby
160
- case result
161
- when Flows::Result::Ok then do_job
162
- when Flows::Results::Err then give_up
163
- end
164
- ```
165
-
166
- But this is too verbose. For this case helpers has methods for matching. Example above may be rewritten like this:
167
-
168
- ```ruby
169
- include Flows::Result::Helpers
170
-
171
- case result
172
- when match_ok then do_job
173
- when match_err then give_up
174
- end
175
- ```
176
-
177
- Moreover, you may specify status when using helper matchers:
178
-
179
- ```ruby
180
- include Flows::Result::Helpers
181
-
182
- case result
183
- when match_ok(:create) then do_create
184
- when match_ok(:update) then do_update
185
- when match_err(:http_error) then retry
186
- when match_err then give_up
187
- end
188
- ```
189
-
190
- ## General Recommendations
191
-
192
- Let's assume that you have some code returning Result Object.
193
-
194
- * if error happened and may be handled somehow - return failure result
195
- * if error happened and cannot be handled - raise exception to abort execution
196
- * if you don't handle any errors for now - don't check result type and use `#unwrap` to access data. It will raise exception when called on a failure result.
@@ -1,139 +0,0 @@
1
- # Result Object :: Do Notation
2
-
3
- This functionality aims to simplify common control flow pattern: when you have to stop execution on a first failure and return this failure.
4
- Do Notation inspired by [Do Notation in dry-rb](https://dry-rb.org/gems/dry-monads/do-notation/) and [Haskell do keyword](https://wiki.haskell.org/Keywords#do).
5
-
6
- Sometimes you have to write something like this:
7
-
8
- ```ruby
9
- class Something
10
- include Flows::Result::Helpers
11
-
12
- def do_job
13
- user_result = fetch_user
14
- return user_result if user_result.err?
15
-
16
- data_result = fetch_data
17
- return data_result if data_result.err?
18
-
19
- calculation_result = calculation(user_result.unwrap[:user], data_result.unwrap)
20
- return calculation_result if user_result.err?
21
-
22
- ok(data: calculation_result.unwrap[:some_field])
23
- end
24
-
25
- private
26
-
27
- def fetch_user
28
- # returns Ok or Err
29
- end
30
-
31
- def fetch_data
32
- # returns Ok or Err
33
- end
34
-
35
- def calculation(_user, _data)
36
- # returns Ok or Err
37
- end
38
- end
39
- ```
40
-
41
- The main idea of the code above is to stop method execution and return failed Result Object if one of the sub-operations is failed. At the moment of failure.
42
-
43
- By using Do Notation feature you may rewrite it like this:
44
-
45
- ```ruby
46
- class SomethingWithDoNotation
47
- include Flows::Result::Helpers
48
- include Flows::Result::Do # enable Do Notation
49
-
50
- do_for(:do_job) # changes behaviour of `yield` in this method
51
- def do_job
52
- user, = yield :user, fetch_user # yield here returns array of one element
53
- data = yield fetch_data # yield here returns a Hash
54
-
55
- ok(data: yield(:some_field, calculation(user, data))[0])
56
- end
57
-
58
- # private method definitions
59
- end
60
- ```
61
-
62
- or like this:
63
-
64
- ```ruby
65
- do_for(:do_job)
66
- def do_job
67
- user = yield(fetch_user)[:user] # yield here and below returns a Hash
68
- data = yield fetch_data
69
-
70
- ok(data: yield(calculation(user, data))[:some_field])
71
- end
72
- ```
73
-
74
- `do_for(:do_job)` makes some simple magic here and allows you to use `yield` inside `do_job` in a non standard way:
75
- to unpack results or instantly leave a method if a failed result provided.
76
-
77
- ## How to use it
78
-
79
- First of all, you have to include `Flows::Result::Do` mixin into your class or module. It adds `do_for` class method.
80
- `do_for` accepts method name as an argument and changes behaviour of `yield` inside this method. By the way, when you are using
81
- `do_for` you cannot pass a block to modified method anymore.
82
-
83
- Then `do_for` method should be used to enable Do Notation for certain methods.
84
-
85
- ```ruby
86
- class MyClass
87
- include Flows::Result::Do
88
-
89
- do_for(:my_method_1)
90
- def my_method_1
91
- # some code
92
- end
93
-
94
- do_for(:my_method_2)
95
- def my_method_2
96
- # some code
97
- end
98
- end
99
- ```
100
-
101
- `yield` in such methods starts working by following rules:
102
-
103
- ```ruby
104
- ok_result = Flows::Result::Ok.new(a: 1, b: 2)
105
- err_result = Flows::Result::Err.new(x: 1, y: 2)
106
-
107
- # following three lines are equivalent
108
- yield(ok_result)
109
- ok_result.unwrap
110
- { a: 1, b: 2 }
111
-
112
- # following three lines are equivalent
113
- yield(:a, :b, ok_result)
114
- ok_result.unwrap.values_at(:a, :b)
115
- [1, 2]
116
-
117
- # following two lines are equivalent
118
- yield(err_result)
119
- return err_result
120
-
121
- # following two lines are equivalent
122
- yield(:x, :y, err_result)
123
- return err_result
124
- ```
125
-
126
- As you may see, `yield` has two forms of usage:
127
-
128
- * `yield(result_value)` - returns unwrapped data Hash for successful results or,
129
- in case of failed result, stops method execution and returns failed `result_value` as a method result.
130
- * `yield(*keys, result_value)` - returns unwrapped data under provided keys as Array for successful results or,
131
- in case of failed result, stops method execution and returns failed `result_value` as a method result.
132
-
133
- ## How it works
134
-
135
- Under the hood `Flows::Result::Do` creates a module and prepends it to your class or module.
136
- Invoking of `do_for(:method_name)` adds special wrapper method to the prepended module. So, when you perform call to
137
- `YourClassOrModule#method_name` - you execute wrapper in the prepended module.
138
-
139
- Check out source code for implementation details.
@@ -1,27 +0,0 @@
1
- module Flows
2
- # Representation of FSM node.
3
- class Node
4
- attr_reader :name, :meta
5
-
6
- def initialize(name:, body:, router:, meta: {}, preprocessor: nil, postprocessor: nil)
7
- @name = name
8
- @body = body
9
- @router = router
10
-
11
- @meta = meta.freeze
12
-
13
- @preprocessor = preprocessor
14
- @postprocessor = postprocessor
15
- end
16
-
17
- def call(input, context:)
18
- input = @preprocessor.call(input, context, @meta) if @preprocessor
19
- output = @body.call(input)
20
- output = @postprocessor.call(output, context, @meta) if @postprocessor
21
-
22
- route = @router.call(output, context: context, meta: @meta)
23
-
24
- [output, route]
25
- end
26
- end
27
- end
@@ -1,52 +0,0 @@
1
- require_relative 'operation/errors'
2
-
3
- require_relative 'operation/dsl'
4
- require_relative 'operation/builder'
5
- require_relative 'operation/executor'
6
-
7
- module Flows
8
- # Operation DSL
9
- module Operation
10
- def self.included(mod)
11
- mod.extend ::Flows::Operation::DSL
12
- end
13
-
14
- include ::Flows::Result::Helpers
15
-
16
- def initialize(method_source: nil, deps: {})
17
- _flows_do_checks
18
-
19
- flow = _flows_make_flow(method_source || self, deps)
20
-
21
- @_flows_executor = _flows_make_executor(flow)
22
- end
23
-
24
- def call(**params)
25
- @_flows_executor.call(**params)
26
- end
27
-
28
- private
29
-
30
- def _flows_do_checks
31
- raise NoStepsError if self.class.steps.empty?
32
- raise NoSuccessShapeError, self if self.class.ok_shapes.nil?
33
- end
34
-
35
- def _flows_make_flow(method_source, deps)
36
- ::Flows::Operation::Builder.new(
37
- steps: self.class.steps,
38
- method_source: method_source,
39
- deps: deps
40
- ).call
41
- end
42
-
43
- def _flows_make_executor(flow)
44
- ::Flows::Operation::Executor.new(
45
- flow: flow,
46
- ok_shapes: self.class.ok_shapes,
47
- err_shapes: self.class.err_shapes,
48
- class_name: self.class.name
49
- )
50
- end
51
- end
52
- end
@@ -1,130 +0,0 @@
1
- require_relative './builder/build_router'
2
-
3
- module Flows
4
- module Operation
5
- # Flow builder
6
- class Builder
7
- attr_reader :steps, :method_source, :deps
8
-
9
- def initialize(steps:, method_source:, deps:)
10
- @method_source = method_source
11
- @steps = steps
12
- @deps = deps
13
-
14
- @step_names = @steps.map { |s| s[:name] }
15
- end
16
-
17
- def call
18
- resolve_wiring!
19
- resolve_bodies!
20
-
21
- nodes = build_nodes
22
- Flows::Flow.new(start_node: nodes.first.name, nodes: nodes)
23
- end
24
-
25
- private
26
-
27
- def resolve_wiring! # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
28
- # we have to disable some linters for performance reasons
29
- # this method can be simplified using `map.with_index`, but while loops is about
30
- # 2x faster for such cases.
31
- index = 0
32
-
33
- while index < @steps.length
34
- current_step = @steps[index]
35
- next_step_name = nil
36
-
37
- inner_index = index + 1
38
- while inner_index < @steps.length
39
- candidate = @steps[inner_index]
40
- candidate_last_track = candidate[:track_path].last
41
-
42
- if candidate[:track_path] == [] || current_step[:track_path].include?(candidate_last_track)
43
- next_step_name = candidate[:name]
44
- break
45
- end
46
-
47
- inner_index += 1
48
- end
49
-
50
- current_step[:next_step] = next_step_name || :term
51
-
52
- index += 1
53
- end
54
- end
55
-
56
- def resolve_bodies!
57
- @steps.each do |step|
58
- step.merge!(
59
- body: step[:custom_body] || resolve_body_from_source(step[:name])
60
- )
61
- end
62
- end
63
-
64
- def resolve_body_from_source(name)
65
- return @deps[name] if @deps.key?(name)
66
-
67
- raise(::Flows::Operation::NoStepImplementationError, name) unless @method_source.respond_to?(name)
68
-
69
- @method_source.method(name)
70
- end
71
-
72
- def build_nodes
73
- @nodes = @steps.map do |step|
74
- Flows::Node.new(
75
- name: step[:name],
76
- body: build_final_body(step),
77
- preprocessor: method(:node_preprocessor),
78
- postprocessor: method(:node_postprocessor),
79
- router: BuildRouter.call(step[:custom_routes], step[:next_step], @step_names),
80
- meta: build_meta(step)
81
- )
82
- end
83
- end
84
-
85
- def build_final_body(step)
86
- case step[:type]
87
- when :step
88
- step[:body]
89
- when :wrapper
90
- build_wrapper_body(step[:body], step[:block])
91
- end
92
- end
93
-
94
- def build_wrapper_body(wrapper, block)
95
- suboperation_class = Class.new do
96
- include ::Flows::Operation
97
- end
98
-
99
- suboperation_class.instance_exec(&block)
100
- suboperation_class.no_shape
101
-
102
- suboperation = suboperation_class.new(method_source: @method_source, deps: @deps)
103
-
104
- lambda do |**options|
105
- wrapper.call(**options) { suboperation.call(**options) }
106
- end
107
- end
108
-
109
- def build_meta(step)
110
- {
111
- type: step[:type],
112
- name: step[:name],
113
- track_path: step[:track_path]
114
- }
115
- end
116
-
117
- def node_preprocessor(_input, context, _meta)
118
- context[:data]
119
- end
120
-
121
- def node_postprocessor(output, context, meta)
122
- output_data = output.ok? ? output.unwrap : output.error
123
- context[:data].merge!(output_data)
124
- context[:last_step] = meta[:name]
125
-
126
- output
127
- end
128
- end
129
- end
130
- end