flows 0.1.0 → 0.5.1

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/test.yml +38 -0
  3. data/.gitignore +9 -1
  4. data/.mdlrc +1 -0
  5. data/.reek.yml +54 -0
  6. data/.rubocop.yml +44 -2
  7. data/.ruby-version +1 -1
  8. data/.yardopts +1 -0
  9. data/CHANGELOG.md +65 -0
  10. data/README.md +186 -256
  11. data/Rakefile +35 -1
  12. data/bin/.rubocop.yml +5 -0
  13. data/bin/all_the_errors +55 -0
  14. data/bin/benchmark +69 -78
  15. data/bin/benchmark_cli/compare.rb +118 -0
  16. data/bin/benchmark_cli/compare/a_plus_b.rb +22 -0
  17. data/bin/benchmark_cli/compare/base.rb +45 -0
  18. data/bin/benchmark_cli/compare/command.rb +47 -0
  19. data/bin/benchmark_cli/compare/ten_steps.rb +22 -0
  20. data/bin/benchmark_cli/examples.rb +23 -0
  21. data/bin/benchmark_cli/examples/.rubocop.yml +19 -0
  22. data/bin/benchmark_cli/examples/a_plus_b/dry_do.rb +23 -0
  23. data/bin/benchmark_cli/examples/a_plus_b/dry_transaction.rb +17 -0
  24. data/bin/benchmark_cli/examples/a_plus_b/flows_do.rb +22 -0
  25. data/bin/benchmark_cli/examples/a_plus_b/flows_railway.rb +13 -0
  26. data/bin/benchmark_cli/examples/a_plus_b/flows_scp.rb +13 -0
  27. data/bin/benchmark_cli/examples/a_plus_b/flows_scp_mut.rb +13 -0
  28. data/bin/benchmark_cli/examples/a_plus_b/flows_scp_oc.rb +21 -0
  29. data/bin/benchmark_cli/examples/a_plus_b/trailblazer.rb +15 -0
  30. data/bin/benchmark_cli/examples/ten_steps/dry_do.rb +70 -0
  31. data/bin/benchmark_cli/examples/ten_steps/dry_transaction.rb +64 -0
  32. data/bin/benchmark_cli/examples/ten_steps/flows_do.rb +69 -0
  33. data/bin/benchmark_cli/examples/ten_steps/flows_railway.rb +58 -0
  34. data/bin/benchmark_cli/examples/ten_steps/flows_scp.rb +58 -0
  35. data/bin/benchmark_cli/examples/ten_steps/flows_scp_mut.rb +58 -0
  36. data/bin/benchmark_cli/examples/ten_steps/flows_scp_oc.rb +66 -0
  37. data/bin/benchmark_cli/examples/ten_steps/trailblazer.rb +60 -0
  38. data/bin/benchmark_cli/helpers.rb +12 -0
  39. data/bin/benchmark_cli/ruby.rb +15 -0
  40. data/bin/benchmark_cli/ruby/command.rb +38 -0
  41. data/bin/benchmark_cli/ruby/method_exec.rb +71 -0
  42. data/bin/benchmark_cli/ruby/self_class.rb +69 -0
  43. data/bin/benchmark_cli/ruby/structs.rb +90 -0
  44. data/bin/console +1 -0
  45. data/bin/docserver +7 -0
  46. data/bin/errors +130 -0
  47. data/bin/errors_cli/contract_error_demo.rb +49 -0
  48. data/bin/errors_cli/di_error_demo.rb +38 -0
  49. data/bin/errors_cli/flow_error_demo.rb +22 -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/.nojekyll +0 -0
  56. data/docs/README.md +13 -0
  57. data/docs/_sidebar.md +2 -0
  58. data/docs/index.html +30 -0
  59. data/flows.gemspec +27 -2
  60. data/forspell.dict +17 -0
  61. data/lefthook.yml +21 -0
  62. data/lib/flows.rb +13 -5
  63. data/lib/flows/contract.rb +402 -0
  64. data/lib/flows/contract/array.rb +55 -0
  65. data/lib/flows/contract/case_eq.rb +43 -0
  66. data/lib/flows/contract/compose.rb +77 -0
  67. data/lib/flows/contract/either.rb +53 -0
  68. data/lib/flows/contract/error.rb +25 -0
  69. data/lib/flows/contract/hash.rb +75 -0
  70. data/lib/flows/contract/hash_of.rb +70 -0
  71. data/lib/flows/contract/helpers.rb +22 -0
  72. data/lib/flows/contract/predicate.rb +34 -0
  73. data/lib/flows/contract/transformer.rb +50 -0
  74. data/lib/flows/contract/tuple.rb +70 -0
  75. data/lib/flows/flow.rb +96 -7
  76. data/lib/flows/flow/errors.rb +29 -0
  77. data/lib/flows/flow/node.rb +132 -0
  78. data/lib/flows/flow/router.rb +29 -0
  79. data/lib/flows/flow/router/custom.rb +59 -0
  80. data/lib/flows/flow/router/errors.rb +11 -0
  81. data/lib/flows/flow/router/simple.rb +25 -0
  82. data/lib/flows/plugin.rb +14 -0
  83. data/lib/flows/plugin/dependency_injector.rb +159 -0
  84. data/lib/flows/plugin/dependency_injector/dependency.rb +24 -0
  85. data/lib/flows/plugin/dependency_injector/dependency_definition.rb +16 -0
  86. data/lib/flows/plugin/dependency_injector/dependency_list.rb +57 -0
  87. data/lib/flows/plugin/dependency_injector/errors.rb +58 -0
  88. data/lib/flows/plugin/implicit_init.rb +45 -0
  89. data/lib/flows/plugin/output_contract.rb +85 -0
  90. data/lib/flows/plugin/output_contract/dsl.rb +48 -0
  91. data/lib/flows/plugin/output_contract/errors.rb +74 -0
  92. data/lib/flows/plugin/output_contract/wrapper.rb +55 -0
  93. data/lib/flows/plugin/profiler.rb +114 -0
  94. data/lib/flows/plugin/profiler/injector.rb +35 -0
  95. data/lib/flows/plugin/profiler/report.rb +48 -0
  96. data/lib/flows/plugin/profiler/report/events.rb +43 -0
  97. data/lib/flows/plugin/profiler/report/flat.rb +41 -0
  98. data/lib/flows/plugin/profiler/report/flat/method_report.rb +81 -0
  99. data/lib/flows/plugin/profiler/report/raw.rb +15 -0
  100. data/lib/flows/plugin/profiler/report/tree.rb +98 -0
  101. data/lib/flows/plugin/profiler/report/tree/calculated_node.rb +116 -0
  102. data/lib/flows/plugin/profiler/report/tree/node.rb +35 -0
  103. data/lib/flows/plugin/profiler/wrapper.rb +53 -0
  104. data/lib/flows/railway.rb +154 -0
  105. data/lib/flows/railway/dsl.rb +18 -0
  106. data/lib/flows/railway/errors.rb +17 -0
  107. data/lib/flows/railway/step.rb +24 -0
  108. data/lib/flows/railway/step_list.rb +38 -0
  109. data/lib/flows/result.rb +189 -2
  110. data/lib/flows/result/do.rb +172 -0
  111. data/lib/flows/result/err.rb +12 -6
  112. data/lib/flows/result/errors.rb +29 -17
  113. data/lib/flows/result/helpers.rb +25 -3
  114. data/lib/flows/result/ok.rb +12 -6
  115. data/lib/flows/shared_context_pipeline.rb +299 -0
  116. data/lib/flows/shared_context_pipeline/dsl.rb +12 -0
  117. data/lib/flows/shared_context_pipeline/dsl/callbacks.rb +38 -0
  118. data/lib/flows/shared_context_pipeline/dsl/tracks.rb +52 -0
  119. data/lib/flows/shared_context_pipeline/errors.rb +17 -0
  120. data/lib/flows/shared_context_pipeline/mutation_step.rb +29 -0
  121. data/lib/flows/shared_context_pipeline/router_definition.rb +21 -0
  122. data/lib/flows/shared_context_pipeline/step.rb +44 -0
  123. data/lib/flows/shared_context_pipeline/track.rb +54 -0
  124. data/lib/flows/shared_context_pipeline/track_list.rb +51 -0
  125. data/lib/flows/shared_context_pipeline/wrap.rb +74 -0
  126. data/lib/flows/util.rb +17 -0
  127. data/lib/flows/util/inheritable_singleton_vars.rb +86 -0
  128. data/lib/flows/util/inheritable_singleton_vars/dup_strategy.rb +98 -0
  129. data/lib/flows/util/inheritable_singleton_vars/isolation_strategy.rb +91 -0
  130. data/lib/flows/util/prepend_to_class.rb +179 -0
  131. data/lib/flows/version.rb +1 -1
  132. metadata +288 -20
  133. data/.travis.yml +0 -8
  134. data/Gemfile.lock +0 -119
  135. data/bin/demo +0 -66
  136. data/bin/examples.rb +0 -159
  137. data/bin/profile_10steps +0 -64
  138. data/bin/ruby_benchmarks +0 -26
  139. data/lib/flows/node.rb +0 -27
  140. data/lib/flows/operation.rb +0 -54
  141. data/lib/flows/operation/builder.rb +0 -130
  142. data/lib/flows/operation/builder/build_router.rb +0 -37
  143. data/lib/flows/operation/dsl.rb +0 -72
  144. data/lib/flows/operation/errors.rb +0 -75
  145. data/lib/flows/operation/executor.rb +0 -78
  146. data/lib/flows/result_router.rb +0 -14
  147. data/lib/flows/router.rb +0 -22
@@ -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,54 +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
- # Operaion DSL
9
- module Operation
10
- def self.included(mod)
11
- mod.instance_variable_set(:@steps, [])
12
- mod.instance_variable_set(:@track_path, [])
13
- mod.extend ::Flows::Operation::DSL
14
- end
15
-
16
- include ::Flows::Result::Helpers
17
-
18
- def initialize(method_source: nil, deps: {})
19
- _flows_do_checks
20
-
21
- flow = _flows_make_flow(method_source || self, deps)
22
-
23
- @_flows_executor = _flows_make_executor(flow)
24
- end
25
-
26
- def call(**params)
27
- @_flows_executor.call(**params)
28
- end
29
-
30
- private
31
-
32
- def _flows_do_checks
33
- raise NoStepsError if self.class.steps.empty?
34
- raise NoSuccessShapeError, self if self.class.ok_shapes.nil?
35
- end
36
-
37
- def _flows_make_flow(method_source, deps)
38
- ::Flows::Operation::Builder.new(
39
- steps: self.class.steps,
40
- method_source: method_source,
41
- deps: deps
42
- ).call
43
- end
44
-
45
- def _flows_make_executor(flow)
46
- ::Flows::Operation::Executor.new(
47
- flow: flow,
48
- ok_shapes: self.class.ok_shapes,
49
- err_shapes: self.class.err_shapes,
50
- class_name: self.class.name
51
- )
52
- end
53
- end
54
- 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.map! 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
@@ -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,72 +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
- include Flows::Result::Helpers
8
-
9
- def step(name, custom_body_or_routes = nil, custom_routes = nil)
10
- if custom_routes
11
- custom_body = custom_body_or_routes
12
- elsif custom_body_or_routes.is_a? Hash
13
- custom_routes = custom_body_or_routes
14
- custom_body = nil
15
- else
16
- custom_routes = nil
17
- custom_body = custom_body_or_routes
18
- end
19
-
20
- @steps << make_step(name, custom_routes: custom_routes, custom_body: custom_body)
21
- end
22
-
23
- def track(name, &block)
24
- track_path_before = @track_path
25
- @track_path += [name]
26
-
27
- @steps << make_step(name, custom_body: ->(**) { ok })
28
- instance_exec(&block)
29
-
30
- @track_path = track_path_before
31
- end
32
-
33
- def wrap(name, custom_body = nil, &block)
34
- @steps << make_step(name, type: :wrapper, custom_body: custom_body, block: block)
35
- end
36
-
37
- def ok_shape(*keys, **code_keys_map)
38
- @ok_shapes = if keys.empty?
39
- code_keys_map
40
- else
41
- { success: keys }
42
- end
43
- end
44
-
45
- def err_shape(*keys, **code_keys_map)
46
- @err_shapes = if keys.empty?
47
- code_keys_map
48
- else
49
- { failure: keys }
50
- end
51
- end
52
-
53
- def no_shape
54
- @ok_shapes = :no_shapes
55
- @err_shapes = :no_shapes
56
- end
57
-
58
- private
59
-
60
- def make_step(name, type: :step, custom_routes: {}, custom_body: nil, block: nil)
61
- {
62
- type: type,
63
- name: name,
64
- custom_routes: custom_routes,
65
- custom_body: custom_body,
66
- block: block,
67
- track_path: @track_path
68
- }
69
- end
70
- end
71
- end
72
- 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