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
@@ -0,0 +1,38 @@
1
+ module Flows
2
+ class Railway
3
+ # @api private
4
+ class StepList
5
+ def initialize
6
+ @list = []
7
+ end
8
+
9
+ def initialize_dup(_other)
10
+ @list = @list.map(&:dup)
11
+ end
12
+
13
+ def add(name:, lambda: nil)
14
+ step = Step.new(name: name, lambda: lambda)
15
+ last_step = @list.last
16
+
17
+ last_step.next_step = name if last_step
18
+
19
+ @list << step
20
+
21
+ self
22
+ end
23
+
24
+ def first_step_name
25
+ @list.first.name
26
+ end
27
+
28
+ # `:reek:FeatureEnvy` is false positive here.
29
+ def to_node_map(method_source)
30
+ @list.map { |step| [step.name, step.to_node(method_source)] }.to_h
31
+ end
32
+
33
+ def empty?
34
+ @list.empty?
35
+ end
36
+ end
37
+ end
38
+ end
@@ -1,11 +1,197 @@
1
1
  module Flows
2
- # Result object with context
2
+ # @abstract
3
+ #
4
+ # Result Object is a way of presenting the result of a calculation. The result may be successful or failed.
5
+ #
6
+ # For example, if you calculate expression `a / b`:
7
+ #
8
+ # * for `a = 6` and `b = 2` result will be successful with data `3`.
9
+ # * for `a = 6` and `b = 0` result will be failed with data, for example, `"Cannot divide by zero"`.
10
+ #
11
+ # Examples of such approach may be found in other libraries and languages:
12
+ #
13
+ # * [Either Monad](https://hackage.haskell.org/package/category-extras-0.52.0/docs/Control-Monad-Either.html)
14
+ # in Haskell.
15
+ # * [Result Type](https://doc.rust-lang.org/std/result/enum.Result.html) in Rust.
16
+ # * [Faraday gem](https://www.rubydoc.info/gems/faraday/Faraday/Response) has `Faraday::Response` object
17
+ # which contains data and status.
18
+ # * [dry-rb Result Monad](https://dry-rb.org/gems/dry-monads/1.3/result/) has `Dry::Monads::Result`.
19
+ #
20
+ # So, why do you need Result Object?
21
+ # Why not just return `nil` on a failure or raise an error (like in the standard library)?
22
+ # Here are several reasons:
23
+ #
24
+ # * Raising errors and exceptions is a [bad way](https://martinfowler.com/articles/replaceThrowWithNotification.html)
25
+ # of handling errors.
26
+ # Moreover, it is slow and looks like `goto`.
27
+ # However, it is still a good way to abort execution on an unexpected error.
28
+ # * Returning `nil` does not work when you have to deal with different types of errors or
29
+ # an error has some data payload.
30
+ # * Using specific Result Objects (like `Faraday::Response`) brings inconsistency -
31
+ # you have to learn how to deal with each new type of Result.
32
+ #
33
+ # That's why `Flows` should have Result Object implementation.
34
+ # If any executable Flows entity will return Result Object with the same API -
35
+ # composing your app components becomes trivial.
36
+ # Result Objects should also be as fast and lightweight as possible.
37
+ #
38
+ # Flows' implementation is inspired mainly by [Rust Result Type](https://doc.rust-lang.org/std/result/enum.Result.html)
39
+ # and focused on following features:
40
+ #
41
+ # * Use idiomatic Ruby: no methods named with first capital letter (`Name(1, 2)`), etc.
42
+ # * Use `case` and `===` (case equality) for matching results and writing routing logic.
43
+ # * Provide helpers for convenient creation and matching of Result Objects ({Helpers}).
44
+ # * Result Object may be successful ({Ok}) or failure ({Err}).
45
+ # * Result Object has an {#status} (some symbol: `:saved`, `:zero_division_error`).
46
+ # * Status usage is optional. Default statuses for successful and failure results are `:ok` and `:err`.
47
+ # * Result may have metadata ({#meta}).
48
+ # Metadata is something unrelated to your business logic
49
+ # (execution time, for example, or some info about who created this result).
50
+ # This data must not be used in business logic, it's for a library code.
51
+ # * Different accessors for successful and failure results -
52
+ # prevents treating failure results as successful and vice versa.
53
+ #
54
+ # ## General Recommendations
55
+ #
56
+ # Let's assume that you have some code returning Result Object.
57
+ #
58
+ # * if an error happened and may be handled somehow - return failure result.
59
+ # * if an error happened and cannot be handled - raise exception to abort execution.
60
+ # * if you don't handle any errors for now - don't check result type and
61
+ # use {#unwrap} to access data. It will raise exception when called on a failure result.
62
+ #
63
+ # @example Creating Result Objects
64
+ # # Successful result with data {a: 1}
65
+ # x = Flows::Result::Ok.new(a: 1)
66
+ #
67
+ # # Failure result with data {msg: 'error'}
68
+ # x = Flows::Result::Err.new(msg: 'error')
69
+ #
70
+ # # Successful result with data {a: 1} and status `:done`
71
+ # x = Flows::Result::Ok.new({ a: 1 }, status: :done)
72
+ #
73
+ # # Failure result with data {msg: 'error'} and status `:http_error`
74
+ # x = Flows::Result::Err.new({ msg: 'error' }, status: :http_error)
75
+ #
76
+ # # Successful result with data {a: 1} and metadata { time: 123 }
77
+ # x = Flows::Result::Ok.new({ a: 1 }, meta: { time: 123 })
78
+ #
79
+ # # Failure result with data {msg: 'error'} and metadata { time: 123 }
80
+ # x = Flows::Result::Err.new({ msg: 'error' }, meta: { time: 123 })
81
+ #
82
+ # @example Create Result Objects using helpers
83
+ # class Demo
84
+ # # You cannot provide metadata using helpers and it's ok:
85
+ # # you shouldn't populate metadata in your business code.
86
+ # # Metadata is designed to use in library code and
87
+ # # when you have to provide some metadata from your library -
88
+ # # just use `.new` instead of helpers.
89
+ # include Flows::Result::Helpers
90
+ #
91
+ # def demo
92
+ # # Successful result with data {a: 1}
93
+ # x = ok(a: 1)
94
+ #
95
+ # # Failure result with data {msg: 'error'}
96
+ # x = err(msg: 'error')
97
+ #
98
+ # # Successful result with data {a: 1} and status `:done`
99
+ # x = ok(:done, a: 1)
100
+ #
101
+ # # Failure result with data {msg: 'error'} and status `:http_error`
102
+ # x = err(:http_error, msg: 'error')
103
+ # end
104
+ # end
105
+ #
106
+ # @example Inspecting Result Objects
107
+ # # Behaviour of any result object:
108
+ # result.status # returns status, example: `:ok`
109
+ # result.meta # returns metadata, example: `{}`
110
+ #
111
+ # # Behaviour specific to successful results:
112
+ # result.ok? # true
113
+ # result.err? # false
114
+ # result.unwrap # returns result data
115
+ # result.error # raises exception
116
+ #
117
+ # # Behaviour specific to failure results:
118
+ # result.ok? # false
119
+ # result.err? # true
120
+ # result.unwrap # raises exception
121
+ # result.error # returns result data
122
+ #
123
+ # @example Matching Results with case
124
+ # case result
125
+ # when Flows::Result::Ok then do_job
126
+ # when Flows::Result::Err then give_up
127
+ # end
128
+ #
129
+ # @example Matching Results with case and helpers
130
+ # class Demo
131
+ # include Flows::Result::Helpers
132
+ #
133
+ # def simple_usage
134
+ # case result
135
+ # when match_ok then do_job
136
+ # when match_err then give_up
137
+ # end
138
+ # end
139
+ #
140
+ # def with_status_matching
141
+ # case result
142
+ # when match_ok(:create) then do_create
143
+ # when match_ok(:update) then do_update
144
+ # when match_err(:http_error) then retry
145
+ # when match_err then give_up
146
+ # end
147
+ # end
148
+ # end
149
+ #
150
+ # @!method ok?
151
+ # @abstract
152
+ # @return [Boolean] `true` if result is successful
153
+ # @!method err?
154
+ # @abstract
155
+ # @return [Boolean] `true` if result is failure
156
+ # @!method unwrap
157
+ # @abstract
158
+ # @return [Object] result data
159
+ # @raise [AccessError] if called on failure object
160
+ # @!method error
161
+ # @abstract
162
+ # @return [Object] result data
163
+ # @raise [AccessError] if called on successful object
164
+ #
165
+ # @since 0.4.0
3
166
  class Result
4
- attr_reader :status, :meta
167
+ # @return [Symbol] status of Result Object, default is `:ok` for successful results
168
+ # and `:err` for failure results.
169
+ attr_reader :status
5
170
 
171
+ # @return [Hash] metadata, don't use it to store business data
172
+ attr_reader :meta
173
+
174
+ # Direct creation of this abstract class is forbidden.
175
+ #
176
+ # @raise [StandardError] you will get an error
6
177
  def initialize(**)
7
178
  raise 'Use Flows::Result::Ok or Flows::Result::Err for build result objects'
8
179
  end
180
+
181
+ # Results are equal if have same type and data.
182
+ #
183
+ # Metadata is ignored in comparison.
184
+ #
185
+ # @return [Boolean]
186
+ def ==(other)
187
+ return false if self.class != other.class
188
+
189
+ (status == other.status) && (data == other.send(:data))
190
+ end
191
+
192
+ private
193
+
194
+ attr_accessor :data
9
195
  end
10
196
  end
11
197
 
@@ -1,29 +1,173 @@
1
1
  module Flows
2
2
  class Result
3
- # Do-notation for Result Objects
3
+ # Do-notation for Result Objects.
4
+ #
5
+ # This functionality aims to simplify common control flow pattern:
6
+ # when you have to stop execution on a first failure and return this failure.
7
+ # Do Notation inspired by [Do Notation in dry-rb](https://dry-rb.org/gems/dry-monads/1.3/do-notation/)
8
+ # and [Haskell do keyword](https://wiki.haskell.org/Keywords#do).
9
+ #
10
+ # Sometimes you have to write something like this:
11
+ #
12
+ # class Something
13
+ # include Flows::Result::Helpers
14
+ #
15
+ # def perform
16
+ # user_result = fetch_user
17
+ # return user_result if user_result.err?
18
+ #
19
+ # data_result = fetch_data
20
+ # return data_result if data_result.err?
21
+ #
22
+ # calculation_result = calculation(user_result.unwrap[:user], data_result.unwrap)
23
+ # return calculation_result if user_result.err?
24
+ #
25
+ # ok(data: calculation_result.unwrap[:some_field])
26
+ # end
27
+ #
28
+ # private
29
+ #
30
+ # def fetch_user
31
+ # # returns Ok or Err
32
+ # end
33
+ #
34
+ # def fetch_data
35
+ # # returns Ok or Err
36
+ # end
37
+ #
38
+ # def calculation(_user, _data)
39
+ # # returns Ok or Err
40
+ # end
41
+ # end
42
+ #
43
+ # The main idea of the code above is to stop method execution and
44
+ # return failed Result Object if one of the sub-operations is failed.
45
+ # At the moment of failure.
46
+ #
47
+ # By using Do Notation feature you may rewrite it like this:
48
+ #
49
+ # class SomethingWithDoNotation
50
+ # include Flows::Result::Helpers
51
+ # extend Flows::Result::Do # enable Do Notation
52
+ #
53
+ # do_notation(:perform) # changes behaviour of `yield` in this method
54
+ # def perform
55
+ # user = yield(fetch_user)[:user] # yield here returns array of one element
56
+ # data = yield fetch_data # yield here returns a Hash
57
+ #
58
+ # ok(
59
+ # data: yield(calculation(user, data))[:some_field]
60
+ # )
61
+ # end
62
+ #
63
+ # # private method definitions
64
+ # end
65
+ #
66
+ # `do_notation(:perform)` makes some wrapping here and allows you to use `yield`
67
+ # inside the `perform` method in a non standard way:
68
+ # to unpack results or instantly leave a method if a failed result was provided.
69
+ #
70
+ # ## How to use it
71
+ #
72
+ # First of all, you have to include `Flows::Result::Do` mixin into your class or module.
73
+ # It adds `do_notation` class method.
74
+ # `do_notation` accepts method name as an argument and changes behaviour of `yield` inside this method.
75
+ # By the way, when you are using `do_notation` you cannot pass a block to modified method anymore.
76
+ #
77
+ # class MyClass
78
+ # extend Flows::Result::Do
79
+ #
80
+ # do_notation(:my_method_1)
81
+ # def my_method_1
82
+ # # some code
83
+ # end
84
+ #
85
+ # do_notation(:my_method_2)
86
+ # def my_method_2
87
+ # # some code
88
+ # end
89
+ # end
90
+ #
91
+ # `yield` in such methods is working by the following rules:
92
+ #
93
+ # ok_result = Flows::Result::Ok.new(a: 1, b: 2)
94
+ # err_result = Flows::Result::Err.new(x: 1, y: 2)
95
+ #
96
+ # # the following three lines are equivalent
97
+ # yield(ok_result)
98
+ # ok_result.unwrap
99
+ # { a: 1, b: 2 }
100
+ #
101
+ # # the following three lines are equivalent
102
+ # yield(:a, :b, ok_result)
103
+ # ok_result.unwrap.values_at(:a, :b)
104
+ # [1, 2]
105
+ #
106
+ # # the following three lines are equivalent
107
+ # return err_result
108
+ # yield(err_result)
109
+ # yield(:x, :y, err_result)
110
+ #
111
+ # As you may see, `yield` has two forms of usage:
112
+ #
113
+ # * `yield(result_value)` - returns unwrapped data Hash for successful results or,
114
+ # in case of failed result, stops method execution and returns failed `result_value` as a method result.
115
+ # * `yield(*keys, result_value)` - returns unwrapped data under provided keys as Array for successful results or,
116
+ # in case of failed result, stops method execution and returns failed `result_value` as a method result.
117
+ #
118
+ # ## How it works
119
+ #
120
+ # Under the hood `Flows::Result::Do` creates a module and prepends it to your class or module.
121
+ # Invoking `do_notation(:method_name)` adds special wrapper method to the prepended module.
122
+ # So, when you perform call to `YourClassOrModule#method_name` - you're executing wrapper in
123
+ # the prepended module.
4
124
  module Do
5
- # DSL for Do-notation
6
- module DSL
7
- def do_for(method_name)
8
- @flows_result_do_module.define_method(method_name) do |*args|
9
- super(*args) do |*fields, result|
10
- case result
11
- when Flows::Result::Ok
12
- fields.any? ? result.unwrap.values_at(*fields) : result.unwrap
13
- when Flows::Result::Err then return result
14
- else raise "Unexpected result: #{result.inspect}. Should be a Flows::Result"
125
+ MOD_VAR_NAME = :@flows_result_module_for_do
126
+
127
+ # Utility functions for Flows::Result::Do.
128
+ #
129
+ # Isolated location prevents polluting user classes with unnecessary methods.
130
+ #
131
+ # @api private
132
+ module Util
133
+ class << self
134
+ def fetch_and_prepend_module(mod)
135
+ module_for_do = mod.instance_variable_get(MOD_VAR_NAME)
136
+ mod.prepend(module_for_do)
137
+ module_for_do
138
+ end
139
+
140
+ # `:reek:TooManyStatements:` - allowed because we have no choice here.
141
+ #
142
+ # `:reek:NestedIterators` - allowed here because here are no iterators.
143
+ def define_wrapper(mod, method_name) # rubocop:disable Metrics/MethodLength
144
+ mod.define_method(method_name) do |*args|
145
+ super(*args) do |*fields, result|
146
+ case result
147
+ when Flows::Result::Ok
148
+ data = result.unwrap
149
+ fields.any? ? data.values_at(*fields) : data
150
+ when Flows::Result::Err then return result
151
+ else raise "Unexpected result: #{result.inspect}. Should be a Flows::Result"
152
+ end
15
153
  end
16
154
  end
17
155
  end
18
156
  end
19
157
  end
20
158
 
21
- def self.included(mod)
22
- patch_mod = Module.new
159
+ # @api private
160
+ def self.extended(mod)
161
+ ::Flows::Util::InheritableSingletonVars::IsolationStrategy.call(
162
+ mod,
163
+ MOD_VAR_NAME => -> { Module.new }
164
+ )
165
+ end
166
+
167
+ def do_notation(method_name)
168
+ prepended_mod = Util.fetch_and_prepend_module(self)
23
169
 
24
- mod.instance_variable_set(:@flows_result_do_module, patch_mod)
25
- mod.prepend(patch_mod)
26
- mod.extend(DSL)
170
+ Util.define_wrapper(prepended_mod, method_name)
27
171
  end
28
172
  end
29
173
  end
@@ -1,25 +1,31 @@
1
1
  module Flows
2
2
  class Result
3
- # Wrapper for failure results
3
+ # Result Object for failure results.
4
+ #
5
+ # @see Flows::Result behaviour described here
4
6
  class Err < Result
5
- attr_reader :error
6
-
7
- def initialize(data, status: :failure, meta: {})
8
- @error = data
7
+ def initialize(data, status: :err, meta: {})
8
+ @data = data
9
9
  @status = status
10
10
  @meta = meta
11
11
  end
12
12
 
13
+ def error
14
+ @data
15
+ end
16
+
17
+ # @return [false]
13
18
  def ok?
14
19
  false
15
20
  end
16
21
 
22
+ # @return [true]
17
23
  def err?
18
24
  true
19
25
  end
20
26
 
21
27
  def unwrap
22
- raise UnwrapError.new(@status, @data, @meta)
28
+ raise AccessError, self
23
29
  end
24
30
  end
25
31
  end