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,37 +0,0 @@
1
- module Flows
2
- module Operation
3
- class Builder
4
- # Router builder
5
- module BuildRouter
6
- class << self
7
- def call(custom_routes, next_step, step_names)
8
- if custom_routes
9
- custom_router(custom_routes, next_step, step_names)
10
- else
11
- Flows::ResultRouter.new(next_step, :term)
12
- end
13
- end
14
-
15
- private
16
-
17
- def custom_router(custom_routes, next_step, step_names)
18
- check_custom_routes(custom_routes, step_names)
19
-
20
- custom_routes[Flows::Result::Ok] ||= next_step
21
- custom_routes[Flows::Result::Err] ||= :term
22
-
23
- Flows::Router.new(custom_routes)
24
- end
25
-
26
- def check_custom_routes(custom_routes, step_names)
27
- custom_routes.values.each do |target|
28
- next if step_names.include?(target) || target == :term
29
-
30
- raise(::Flows::Operation::NoStepDefinedError, target)
31
- end
32
- end
33
- end
34
- end
35
- end
36
- end
37
- end
@@ -1,93 +0,0 @@
1
- module Flows
2
- module Operation
3
- # DSL methods for operation
4
- module DSL
5
- attr_reader :steps, :ok_shapes, :err_shapes
6
-
7
- def self.extended(mod, steps = nil, ok_shapes = nil, err_shapes = nil)
8
- mod.instance_variable_set(:@steps, steps || [])
9
- mod.instance_variable_set(:@track_path, [])
10
- mod.instance_variable_set(:@ok_shapes, ok_shapes)
11
- mod.instance_variable_set(:@err_shapes, err_shapes)
12
-
13
- mod.class_exec do
14
- def self.inherited(subclass)
15
- ::Flows::Operation::DSL.extended(subclass, steps.map(&:dup), ok_shapes, err_shapes)
16
- super
17
- end
18
- end
19
- end
20
-
21
- include Flows::Result::Helpers
22
-
23
- def step(name, custom_body_or_routes = nil, custom_routes = nil)
24
- if custom_routes
25
- custom_body = custom_body_or_routes
26
- elsif custom_body_or_routes.is_a? Hash
27
- custom_routes = custom_body_or_routes
28
- custom_body = nil
29
- else
30
- custom_routes = nil
31
- custom_body = custom_body_or_routes
32
- end
33
-
34
- @steps << make_step(name, custom_routes: custom_routes, custom_body: custom_body)
35
- end
36
-
37
- def track(name, &block)
38
- track_path_before = @track_path
39
- @track_path += [name]
40
-
41
- @steps << make_step(name, custom_body: ->(**) { ok })
42
- instance_exec(&block)
43
-
44
- @track_path = track_path_before
45
- end
46
-
47
- def routes(routes_hash)
48
- routes_hash
49
- end
50
-
51
- alias when_ok match_ok
52
- alias when_err match_err
53
-
54
- def wrap(name, custom_body = nil, &block)
55
- @steps << make_step(name, type: :wrapper, custom_body: custom_body, block: block)
56
- end
57
-
58
- def ok_shape(*keys, **code_keys_map)
59
- @ok_shapes = if keys.empty?
60
- code_keys_map
61
- else
62
- { success: keys }
63
- end
64
- end
65
-
66
- def err_shape(*keys, **code_keys_map)
67
- @err_shapes = if keys.empty?
68
- code_keys_map
69
- else
70
- { failure: keys }
71
- end
72
- end
73
-
74
- def no_shape
75
- @ok_shapes = :no_shapes
76
- @err_shapes = :no_shapes
77
- end
78
-
79
- private
80
-
81
- def make_step(name, type: :step, custom_routes: {}, custom_body: nil, block: nil)
82
- {
83
- type: type,
84
- name: name,
85
- custom_routes: custom_routes,
86
- custom_body: custom_body,
87
- block: block,
88
- track_path: @track_path
89
- }
90
- end
91
- end
92
- end
93
- end
@@ -1,75 +0,0 @@
1
- module Flows
2
- module Operation
3
- # rubocop:disable Style/Documentation
4
- class NoSuccessShapeError < ::Flows::Error
5
- def message
6
- 'Missing success output shapes'
7
- end
8
- end
9
-
10
- class NoFailureShapeError < ::Flows::Error
11
- def message
12
- 'Missing failure output shape'
13
- end
14
- end
15
-
16
- class NoStepsError < ::Flows::Error
17
- def message
18
- 'No steps defined'
19
- end
20
- end
21
-
22
- class NoStepImplementationError < ::Flows::Error
23
- def initialize(step_name)
24
- @step_name = step_name
25
- end
26
-
27
- def message
28
- "Missing step implementation for #{@step_name}"
29
- end
30
- end
31
-
32
- class NoStepDefinedError < ::Flows::Error
33
- def initialize(step_name)
34
- @step_name = step_name
35
- end
36
-
37
- def message
38
- "Missing step or track definition: #{@step_name}"
39
- end
40
- end
41
-
42
- class MissingOutputError < ::Flows::Error
43
- def initialize(required_keys, actual_keys)
44
- @missing_keys = required_keys - actual_keys
45
- end
46
-
47
- def message
48
- "Missing keys in output: #{@missing_keys.join(', ')}"
49
- end
50
- end
51
-
52
- class UnexpectedSuccessStatusError < ::Flows::Error
53
- def initialize(actual_status, supported_statuses)
54
- @actual_status = actual_status.inspect
55
- @supported_statuses = supported_statuses.map(&:inspect).join(', ')
56
- end
57
-
58
- def message
59
- "Unexpeted success result status: `#{@actual_status}`, supported statuses: `#{@supported_statuses}`"
60
- end
61
- end
62
-
63
- class UnexpectedFailureStatusError < ::Flows::Error
64
- def initialize(actual_status, supported_statuses)
65
- @actual_status = actual_status.inspect
66
- @supported_statuses = supported_statuses.map(&:inspect).join(', ')
67
- end
68
-
69
- def message
70
- "Unexpeted failure result status: `#{@actual_status}`, supported statuses: `#{@supported_statuses}`"
71
- end
72
- end
73
- # rubocop:enable Style/Documentation
74
- end
75
- end
@@ -1,78 +0,0 @@
1
- module Flows
2
- module Operation
3
- # Runner for operation steps
4
- class Executor
5
- include ::Flows::Result::Helpers
6
-
7
- def initialize(flow:, ok_shapes:, err_shapes:, class_name:)
8
- @flow = flow
9
-
10
- @ok_shapes = ok_shapes
11
- @err_shapes = err_shapes
12
- @operation_class_name = class_name
13
- end
14
-
15
- def call(**params)
16
- context = { data: params }
17
- last_result = @flow.call(nil, context: context)
18
-
19
- build_result(last_result, context)
20
- end
21
-
22
- private
23
-
24
- def build_result(last_result, context)
25
- status = last_result.status
26
-
27
- case last_result
28
- when Flows::Result::Ok then build_success_result(status, context)
29
- when Flows::Result::Err then build_failure_result(status, context, last_result)
30
- end
31
- end
32
-
33
- def build_success_result(status, context)
34
- return ok(status, context[:data]) if @ok_shapes == :no_shapes
35
-
36
- shape = @ok_shapes[status]
37
- raise ::Flows::Operation::UnexpectedSuccessStatusError.new(status, @ok_shapes.keys) if shape.nil?
38
-
39
- data = extract_data(context[:data], shape)
40
-
41
- ok(status, data)
42
- end
43
-
44
- def build_failure_result(status, context, last_result)
45
- raise ::Flows::Operation::NoFailureShapeError if @err_shapes.nil?
46
-
47
- meta = build_meta(context, last_result)
48
-
49
- return Flows::Result::Err.new(context[:data], status: status, meta: meta) if @err_shapes == :no_shapes
50
-
51
- shape = @err_shapes[status]
52
- raise ::Flows::Operation::UnexpectedFailureStatusError.new(status, @err_shapes.keys) if shape.nil?
53
-
54
- data = extract_data(context[:data], shape)
55
-
56
- Flows::Result::Err.new(data, status: status, meta: meta)
57
- end
58
-
59
- def extract_data(output, keys)
60
- raise ::Flows::Operation::MissingOutputError.new(keys, output.keys) unless keys.all? { |key| output.key?(key) }
61
-
62
- output.slice(*keys)
63
- end
64
-
65
- def build_meta(context, last_result)
66
- meta = {
67
- operation: @operation_class_name,
68
- step: context[:last_step],
69
- context_data: context[:data]
70
- }
71
-
72
- meta[:nested_metadata] = last_result.meta if last_result.meta.any?
73
-
74
- meta
75
- end
76
- end
77
- end
78
- end
@@ -1,68 +0,0 @@
1
- module Flows
2
- module Railway
3
- # Flow builder
4
- class Builder
5
- attr_reader :steps, :method_source, :deps
6
-
7
- def initialize(steps:, method_source:, deps:)
8
- @method_source = method_source
9
- @steps = steps
10
- @deps = deps
11
- end
12
-
13
- def call
14
- resolve_bodies_and_wiring!
15
-
16
- nodes = build_nodes
17
- Flows::Flow.new(start_node: nodes.first.name, nodes: nodes)
18
- end
19
-
20
- private
21
-
22
- def resolve_bodies_and_wiring!
23
- index = 0
24
-
25
- while index < @steps.length
26
- current_step = @steps[index]
27
-
28
- current_step[:next_step] = @steps[index + 1]&.fetch(:name) || :term
29
- current_step[:body] = current_step[:custom_body] || resolve_body_from_source(current_step[:name])
30
-
31
- index += 1
32
- end
33
- end
34
-
35
- def resolve_body_from_source(name)
36
- return @deps[name] if @deps.key?(name)
37
-
38
- raise(::Flows::Railway::NoStepImplementationError, name) unless @method_source.respond_to?(name)
39
-
40
- @method_source.method(name)
41
- end
42
-
43
- def build_nodes
44
- @nodes = @steps.map do |step|
45
- Flows::Node.new(
46
- name: step[:name],
47
- body: step[:body],
48
- preprocessor: method(:node_preprocessor),
49
- postprocessor: method(:node_postprocessor),
50
- router: Flows::ResultRouter.new(step[:next_step], :term),
51
-
52
- meta: { name: step[:name] }
53
- )
54
- end
55
- end
56
-
57
- def node_preprocessor(input, _context, _meta)
58
- input.unwrap
59
- end
60
-
61
- def node_postprocessor(output, context, meta)
62
- context[:last_step] = meta[:name]
63
-
64
- output
65
- end
66
- end
67
- end
68
- end
@@ -1,23 +0,0 @@
1
- module Flows
2
- module Railway
3
- # Runner for railway steps
4
- class Executor
5
- include ::Flows::Result::Helpers
6
-
7
- def initialize(flow:, class_name:)
8
- @flow = flow
9
- @railway_class_name = class_name
10
- end
11
-
12
- def call(**params)
13
- context = {}
14
- last_result = @flow.call(ok(params), context: context)
15
-
16
- last_result.meta[:railway] = @railway_class_name
17
- last_result.meta[:last_step] = context[:last_step]
18
-
19
- last_result
20
- end
21
- end
22
- end
23
- end
@@ -1,14 +0,0 @@
1
- module Flows
2
- # Node router for simple case when result must be a `Flows::Result`
3
- # and we don't care about result status key
4
- class ResultRouter
5
- def initialize(success_route, failure_route)
6
- @success_route = success_route
7
- @failure_route = failure_route
8
- end
9
-
10
- def call(output, **)
11
- output.ok? ? @success_route : @failure_route
12
- end
13
- end
14
- end
@@ -1,22 +0,0 @@
1
- module Flows
2
- # Node router: defines predicate rules to calculate next node.
3
- class Router
4
- class Error < Flows::Error; end
5
- class NoRouteError < Error; end
6
-
7
- def initialize(route_hash, preprocessor: nil)
8
- @route_def = route_hash
9
- @preprocessor = preprocessor
10
- end
11
-
12
- def call(output, context:, meta:)
13
- data = @preprocessor ? @preprocessor.call(output, context, meta) : output
14
-
15
- @route_def.each_pair do |predicate, route|
16
- return route if predicate === data # rubocop:disable Style/CaseEquality
17
- end
18
-
19
- raise NoRouteError, "no route found found for output: #{output.inspect}"
20
- end
21
- end
22
- end