flows 0.4.0 → 0.5.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.
- checksums.yaml +4 -4
- data/.mdlrc +1 -1
- data/.reek.yml +12 -0
- data/CHANGELOG.md +16 -0
- data/Gemfile.lock +1 -1
- data/README.md +12 -2
- data/Rakefile +1 -1
- data/bin/all_the_errors +8 -0
- data/bin/errors +12 -0
- data/bin/errors_cli/flow_error_demo.rb +22 -0
- data/docs/README.md +1 -1
- data/lib/flows/contract/case_eq.rb +3 -1
- data/lib/flows/flow/errors.rb +29 -0
- data/lib/flows/flow/node.rb +1 -0
- data/lib/flows/flow/router/custom.rb +5 -0
- data/lib/flows/flow/router/simple.rb +5 -0
- data/lib/flows/flow/router.rb +4 -0
- data/lib/flows/flow.rb +21 -0
- data/lib/flows/plugin/dependency_injector.rb +5 -5
- data/lib/flows/plugin/output_contract/dsl.rb +15 -3
- data/lib/flows/plugin/output_contract/wrapper.rb +14 -12
- data/lib/flows/plugin/output_contract.rb +1 -0
- data/lib/flows/plugin/profiler/injector.rb +35 -0
- data/lib/flows/plugin/profiler/report/events.rb +43 -0
- data/lib/flows/plugin/profiler/report/flat/method_report.rb +81 -0
- data/lib/flows/plugin/profiler/report/flat.rb +41 -0
- data/lib/flows/plugin/profiler/report/raw.rb +15 -0
- data/lib/flows/plugin/profiler/report/tree/calculated_node.rb +116 -0
- data/lib/flows/plugin/profiler/report/tree/node.rb +35 -0
- data/lib/flows/plugin/profiler/report/tree.rb +98 -0
- data/lib/flows/plugin/profiler/report.rb +48 -0
- data/lib/flows/plugin/profiler/wrapper.rb +53 -0
- data/lib/flows/plugin/profiler.rb +114 -0
- data/lib/flows/plugin.rb +1 -0
- data/lib/flows/railway/dsl.rb +3 -2
- data/lib/flows/result/do.rb +6 -8
- data/lib/flows/shared_context_pipeline/dsl/callbacks.rb +38 -0
- data/lib/flows/shared_context_pipeline/dsl/tracks.rb +52 -0
- data/lib/flows/shared_context_pipeline/dsl.rb +5 -56
- data/lib/flows/shared_context_pipeline/mutation_step.rb +6 -8
- data/lib/flows/shared_context_pipeline/step.rb +6 -8
- data/lib/flows/shared_context_pipeline/track.rb +2 -15
- data/lib/flows/shared_context_pipeline/track_list.rb +11 -6
- data/lib/flows/shared_context_pipeline/wrap.rb +64 -0
- data/lib/flows/shared_context_pipeline.rb +109 -26
- data/lib/flows/util/inheritable_singleton_vars/dup_strategy.rb +40 -51
- data/lib/flows/util/inheritable_singleton_vars/isolation_strategy.rb +39 -52
- data/lib/flows/util/inheritable_singleton_vars.rb +22 -15
- data/lib/flows/util/prepend_to_class.rb +43 -9
- data/lib/flows/version.rb +1 -1
- metadata +18 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8910a47890a3e15f9a79dbb3f7e6255f57cdf2232b9257e05dbdfd39b252d4f7
|
4
|
+
data.tar.gz: bb71db93b1128f3c9f7a7dfb5e705c4ec4e6bd3615a19479bb5f68ba97d63b05
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ea2894cb2a0472d84968d89c8f7ea3d0013e89fa1d7fc0864ba0cec223683c29950e24cff43b161204da0aa3aadf3969eb848539e828471de82a6b4beef500f1
|
7
|
+
data.tar.gz: b12a300213344b4baab7a0f112931018a3a6f74bfee143687111261d692478bd6f2edf1436fb4f8819aae0afca4d4172d194b22e672a1cf9b3838d1b2b749002
|
data/.mdlrc
CHANGED
@@ -1 +1 @@
|
|
1
|
-
rules "~MD013", "~MD033"
|
1
|
+
rules "~MD013", "~MD033", "~MD024"
|
data/.reek.yml
CHANGED
@@ -22,6 +22,7 @@ detectors:
|
|
22
22
|
DataClump:
|
23
23
|
exclude:
|
24
24
|
- Flows::Result::Helpers
|
25
|
+
- Flows::SharedContextPipeline::DSL::Tracks
|
25
26
|
IrresponsibleModule:
|
26
27
|
exclude:
|
27
28
|
- Flows::SharedContextPipeline
|
@@ -29,14 +30,25 @@ detectors:
|
|
29
30
|
exclude:
|
30
31
|
- Flows::SharedContextPipeline#call
|
31
32
|
- Flows::Contract # too many false positives here
|
33
|
+
- Flows::Plugin::Profiler::Report#events
|
32
34
|
TooManyStatements:
|
33
35
|
exclude:
|
34
36
|
- Flows::SharedContextPipeline#call
|
37
|
+
- Flows::Plugin::Profiler::Wrapper#make_instance_wrapper
|
38
|
+
- Flows::Plugin::Profiler::Wrapper#make_singleton_wrapper
|
35
39
|
- initialize
|
36
40
|
DuplicateMethodCall:
|
37
41
|
exclude:
|
38
42
|
- Flows::SharedContextPipeline#call
|
43
|
+
- Flows::Plugin::Profiler::Wrapper#make_instance_wrapper
|
44
|
+
- Flows::Plugin::Profiler::Wrapper#make_singleton_wrapper
|
39
45
|
- 'length'
|
40
46
|
MissingSafeMethod:
|
41
47
|
exclude:
|
42
48
|
- Flows::Contract
|
49
|
+
BooleanParameter:
|
50
|
+
exclude:
|
51
|
+
- Flows::Plugin::OutputContract::DSL
|
52
|
+
TooManyMethods:
|
53
|
+
exclude:
|
54
|
+
- Flows::Plugin::Profiler::Report::Tree::Node
|
data/CHANGELOG.md
CHANGED
@@ -16,6 +16,21 @@ Types of changes:
|
|
16
16
|
|
17
17
|
## [Unreleased]
|
18
18
|
|
19
|
+
## [0.5.0] - 2020-05-18
|
20
|
+
|
21
|
+
### Added
|
22
|
+
|
23
|
+
* `Flows::SharedContextPipeline` wrap DSL, [issue](https://github.com/ffloyd/flows/issues/7)
|
24
|
+
* `Flows::Flow` routing integrity check on initialization
|
25
|
+
* `Flows::Plugin::OutputContract` skip contract DSL method
|
26
|
+
* `Flows::Plugin::Profiler` introduced. Report types: raw, tree and flat.
|
27
|
+
|
28
|
+
### Changed
|
29
|
+
|
30
|
+
* `Flows::SharedContextPipeline` callback API, [issue](https://github.com/ffloyd/flows/issues/6)
|
31
|
+
* `Flows::Util` modules API, [issue](https://github.com/ffloyd/flows/issues/11)
|
32
|
+
* `Flows::Contract::CaseEq` default error expanded to present more context
|
33
|
+
|
19
34
|
## [0.4.0] - 2020-04-21
|
20
35
|
|
21
36
|
### Added
|
@@ -39,4 +54,5 @@ Types of changes:
|
|
39
54
|
target module did not included directly into class.
|
40
55
|
|
41
56
|
[unreleased]: https://github.com/ffloyd/flows/compare/v0.4.0...HEAD
|
57
|
+
[0.5.0]: https://github.com/ffloyd/flows/compare/v0.4.0...v0.5.0
|
42
58
|
[0.4.0]: https://github.com/ffloyd/flows/compare/v0.3.0...v0.4.0
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Flows
|
2
2
|
|
3
|
-
[](https://github.com/ffloyd/flows/actions)
|
4
4
|
[](https://codecov.io/gh/ffloyd/flows)
|
5
5
|
[](https://badge.fury.io/rb/flows)
|
6
6
|
|
@@ -19,7 +19,7 @@ Also `flows` is faster than Ruby's alternatives.
|
|
19
19
|
Add this line to your application's Gemfile:
|
20
20
|
|
21
21
|
```ruby
|
22
|
-
gem 'flows', '~> 0.
|
22
|
+
gem 'flows', '~> 0.5'
|
23
23
|
```
|
24
24
|
|
25
25
|
And then execute:
|
@@ -251,6 +251,16 @@ version. _No exceptions._
|
|
251
251
|
|
252
252
|
Commit with a version bump should contain _only_ version bump and CHANGELOG.md update.
|
253
253
|
|
254
|
+
### GitHub Flow
|
255
|
+
|
256
|
+
Since `v0.4.0` this repo strictly follow [GitHub
|
257
|
+
Flow](https://guides.github.com/introduction/flow/) with some additions:
|
258
|
+
|
259
|
+
* branch naming using dash: `improved-contexts`
|
260
|
+
* use [references to
|
261
|
+
issues](https://help.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword)
|
262
|
+
in commit messages and make links to issues in CHANGELOG.md
|
263
|
+
|
254
264
|
### Planned features for v1.0.0
|
255
265
|
|
256
266
|
* validation framework
|
data/Rakefile
CHANGED
@@ -14,7 +14,7 @@ Reek::Rake::Task.new
|
|
14
14
|
Inch::Rake::Suggest.new
|
15
15
|
|
16
16
|
PATHS_TO_SPELLCHECK = ['.'].freeze
|
17
|
-
PATHS_FOR_MDL = ['README.md', Dir.glob('docs/**/*.md')].flatten.freeze
|
17
|
+
PATHS_FOR_MDL = ['README.md', 'CHANGELOG.md', Dir.glob('docs/**/*.md')].flatten.freeze
|
18
18
|
|
19
19
|
desc 'Run self spellchecking'
|
20
20
|
task :spellcheck do |_task|
|
data/bin/all_the_errors
CHANGED
@@ -45,3 +45,11 @@ echo "Shared Context Pipeline"
|
|
45
45
|
echo "---------------------------"
|
46
46
|
"${BASH_SOURCE%/*}/errors" scp no_steps
|
47
47
|
"${BASH_SOURCE%/*}/errors" scp no_step_impl
|
48
|
+
|
49
|
+
echo
|
50
|
+
echo
|
51
|
+
echo "---------------------------"
|
52
|
+
echo "Flow"
|
53
|
+
echo "---------------------------"
|
54
|
+
"${BASH_SOURCE%/*}/errors" flow invalid_node_route
|
55
|
+
"${BASH_SOURCE%/*}/errors" flow no_first_node
|
data/bin/errors
CHANGED
@@ -16,6 +16,7 @@ require_relative 'errors_cli/oc_error_demo'
|
|
16
16
|
require_relative 'errors_cli/railway_error_demo'
|
17
17
|
require_relative 'errors_cli/result_error_demo'
|
18
18
|
require_relative 'errors_cli/scp_error_demo'
|
19
|
+
require_relative 'errors_cli/flow_error_demo'
|
19
20
|
|
20
21
|
class ErrorsCLI
|
21
22
|
extend GLI::App
|
@@ -113,6 +114,17 @@ class ErrorsCLI
|
|
113
114
|
SCPErrorDemo.no_step_impl
|
114
115
|
end
|
115
116
|
end
|
117
|
+
|
118
|
+
desc 'Flow errors'
|
119
|
+
command :flow do |cmd|
|
120
|
+
make_cmd cmd, 'No first node', :no_first_node do
|
121
|
+
FlowErrorDemo.no_first_node
|
122
|
+
end
|
123
|
+
|
124
|
+
make_cmd cmd, 'Invalid Node route', :invalid_node_route do
|
125
|
+
FlowErrorDemo.invalid_node_route
|
126
|
+
end
|
127
|
+
end
|
116
128
|
end
|
117
129
|
|
118
130
|
exit ErrorsCLI.run(ARGV)
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module FlowErrorDemo
|
2
|
+
class << self
|
3
|
+
def no_first_node
|
4
|
+
Flows::Flow.new(
|
5
|
+
start_node: :first,
|
6
|
+
node_map: {}
|
7
|
+
)
|
8
|
+
end
|
9
|
+
|
10
|
+
def invalid_node_route
|
11
|
+
Flows::Flow.new(
|
12
|
+
start_node: :first,
|
13
|
+
node_map: {
|
14
|
+
first: Flows::Flow::Node.new(
|
15
|
+
body: ->(_) {},
|
16
|
+
router: Flows::Flow::Router::Custom.new(a: :b)
|
17
|
+
)
|
18
|
+
}
|
19
|
+
)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/docs/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Flows
|
2
2
|
|
3
|
-
[](https://github.com/ffloyd/flows/actions)
|
4
4
|
[](https://codecov.io/gh/ffloyd/flows)
|
5
5
|
[](https://badge.fury.io/rb/flows)
|
6
6
|
|
@@ -30,7 +30,9 @@ module Flows
|
|
30
30
|
# @see Contract#check!
|
31
31
|
def check!(other)
|
32
32
|
unless @object === other
|
33
|
-
value_error =
|
33
|
+
value_error =
|
34
|
+
@error_message ||
|
35
|
+
"must match `#{@object.inspect}`, but has class `#{other.class.inspect}` and value `#{other.inspect}`"
|
34
36
|
raise Error.new(other, value_error)
|
35
37
|
end
|
36
38
|
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Flows
|
2
|
+
class Flow
|
3
|
+
# Base class for {Flow} error
|
4
|
+
class Error < StandardError; end
|
5
|
+
|
6
|
+
# Raised when router has an impossible route.
|
7
|
+
class InvalidNodeRouteError < Error
|
8
|
+
def initialize(node_name, route_destination)
|
9
|
+
@node_name = node_name.inspect
|
10
|
+
@route_destination = route_destination.inspect
|
11
|
+
end
|
12
|
+
|
13
|
+
def message
|
14
|
+
"Node `#{@node_name}` has a route to `#{@route_destination}`, but node `#{@route_destination}` is missing."
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Raised when router has an impossible route.
|
19
|
+
class InvalidFirstNodeError < Error
|
20
|
+
def initialize(node_name)
|
21
|
+
@node_name = node_name.inspect
|
22
|
+
end
|
23
|
+
|
24
|
+
def message
|
25
|
+
"`#{@node_name}` is a first node name, but node `#{@node_name}` is missing."
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/flows/flow/node.rb
CHANGED
data/lib/flows/flow/router.rb
CHANGED
@@ -15,6 +15,10 @@ module Flows
|
|
15
15
|
# @param result [Flows::Result] Result Object, output of a {Node} execution.
|
16
16
|
# @return [Symbol] name of the next node or a special symbol `:end`.
|
17
17
|
# @raise [NoRouteError] if cannot determine a route.
|
18
|
+
#
|
19
|
+
# @!method destinations
|
20
|
+
# @abstract
|
21
|
+
# @return [Array<Symbol>] names of all the possible destination nodes
|
18
22
|
class Router
|
19
23
|
end
|
20
24
|
end
|
data/lib/flows/flow.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require_relative 'flow/errors'
|
1
2
|
require_relative 'flow/node'
|
2
3
|
require_relative 'flow/router'
|
3
4
|
|
@@ -66,9 +67,13 @@ module Flows
|
|
66
67
|
class Flow
|
67
68
|
# @param start_node [Symbol] name of the entry node.
|
68
69
|
# @param node_map [Hash<Symbol, Node>] keys are node names, values are nodes.
|
70
|
+
# @raise [Flows::Flow::InvalidNodeRouteError] when some node has invalid routing destination.
|
71
|
+
# @raise [Flows::Flow::InvalidFirstNodeError] when first node is not presented in node map.
|
69
72
|
def initialize(start_node:, node_map:)
|
70
73
|
@start_node = start_node
|
71
74
|
@node_map = node_map
|
75
|
+
|
76
|
+
check_routing_integrity
|
72
77
|
end
|
73
78
|
|
74
79
|
# Executes a flow.
|
@@ -85,5 +90,21 @@ module Flows
|
|
85
90
|
|
86
91
|
input
|
87
92
|
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def check_routing_integrity
|
97
|
+
raise InvalidFirstNodeError, @start_node unless @node_map.key?(@start_node)
|
98
|
+
|
99
|
+
@node_map.each { |node_name, node| check_node_routing_integrity(node_name, node) }
|
100
|
+
end
|
101
|
+
|
102
|
+
def check_node_routing_integrity(node_name, node)
|
103
|
+
node.router.destinations.each do |destination_name|
|
104
|
+
if destination_name != :end && !@node_map.key?(destination_name)
|
105
|
+
raise InvalidNodeRouteError.new(node_name, destination_name)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
88
109
|
end
|
89
110
|
end
|
@@ -88,11 +88,12 @@ module Flows
|
|
88
88
|
# Placeholder for empty value. We cannot use `nil` because value can be `nil`.
|
89
89
|
NO_VALUE = :__no_value__
|
90
90
|
|
91
|
-
Flows::Util::InheritableSingletonVars::DupStrategy.
|
92
|
-
self,
|
91
|
+
SingletonVarsSetup = Flows::Util::InheritableSingletonVars::DupStrategy.make_module(
|
93
92
|
'@dependencies' => {}
|
94
93
|
)
|
95
94
|
|
95
|
+
include SingletonVarsSetup
|
96
|
+
|
96
97
|
# @api private
|
97
98
|
module DSL
|
98
99
|
attr_reader :dependencies
|
@@ -133,8 +134,7 @@ module Flows
|
|
133
134
|
|
134
135
|
singleton_class.prepend InheritanceCallback
|
135
136
|
|
136
|
-
|
137
|
-
module InitializePatch
|
137
|
+
InitializerWrapper = Util::PrependToClass.make_module do
|
138
138
|
def initialize(*args, **kwargs, &block) # rubocop:disable Metrics/MethodLength
|
139
139
|
klass = self.class
|
140
140
|
DependencyList.new(
|
@@ -153,7 +153,7 @@ module Flows
|
|
153
153
|
end
|
154
154
|
end
|
155
155
|
|
156
|
-
|
156
|
+
include InitializerWrapper
|
157
157
|
end
|
158
158
|
end
|
159
159
|
end
|
@@ -9,12 +9,17 @@ module Flows
|
|
9
9
|
# Hash of contracts for failure results.
|
10
10
|
attr_reader :failure_contracts
|
11
11
|
|
12
|
-
|
13
|
-
|
12
|
+
# Is contract check and transformation disabled
|
13
|
+
attr_reader :skip_output_contract_flag
|
14
|
+
|
15
|
+
SingletonVarsSetup = Flows::Util::InheritableSingletonVars::DupStrategy.make_module(
|
14
16
|
'@success_contracts' => {},
|
15
|
-
'@failure_contracts' => {}
|
17
|
+
'@failure_contracts' => {},
|
18
|
+
'@skip_output_contract_flag' => false
|
16
19
|
)
|
17
20
|
|
21
|
+
include SingletonVarsSetup
|
22
|
+
|
18
23
|
# Defines a contract for a successful result with specific status.
|
19
24
|
#
|
20
25
|
# @param status [Symbol] Corresponding result status.
|
@@ -30,6 +35,13 @@ module Flows
|
|
30
35
|
def failure_with(status, &contract_block)
|
31
36
|
failure_contracts[status] = Flows::Contract.make(&contract_block)
|
32
37
|
end
|
38
|
+
|
39
|
+
# Disables contract check and transformation for current class and children.
|
40
|
+
#
|
41
|
+
# @param enable [Boolean] if true - contracts are disabled
|
42
|
+
def skip_output_contract(enable = true)
|
43
|
+
@skip_output_contract_flag = enable
|
44
|
+
end
|
33
45
|
end
|
34
46
|
end
|
35
47
|
end
|
@@ -8,16 +8,16 @@ module Flows
|
|
8
8
|
def initialize(*args, &block)
|
9
9
|
super(*args, &block)
|
10
10
|
klass = self.class
|
11
|
-
raise NoContractError, klass if klass.success_contracts.empty?
|
11
|
+
raise NoContractError, klass if klass.success_contracts.empty? && !klass.skip_output_contract_flag
|
12
12
|
end
|
13
13
|
|
14
14
|
def call(*args, &block)
|
15
15
|
result = super(*args, &block)
|
16
16
|
klass = self.class
|
17
17
|
|
18
|
-
|
18
|
+
return result if klass.skip_output_contract_flag
|
19
19
|
|
20
|
-
Util.transform_result(klass,
|
20
|
+
Util.transform_result(klass, result)
|
21
21
|
|
22
22
|
result
|
23
23
|
end
|
@@ -28,16 +28,9 @@ module Flows
|
|
28
28
|
# @api private
|
29
29
|
module Util
|
30
30
|
class << self
|
31
|
-
def
|
32
|
-
|
33
|
-
|
34
|
-
status = result.status
|
35
|
-
contracts = result.ok? ? klass.success_contracts : klass.failure_contracts
|
31
|
+
def transform_result(klass, result)
|
32
|
+
contract = Util.contract_for(klass, result)
|
36
33
|
|
37
|
-
contracts[status] || raise(StatusError.new(klass, result, contracts.keys))
|
38
|
-
end
|
39
|
-
|
40
|
-
def transform_result(klass, contract, result)
|
41
34
|
data = result.send(:data)
|
42
35
|
|
43
36
|
transformed_result = contract.transform(data)
|
@@ -45,6 +38,15 @@ module Flows
|
|
45
38
|
|
46
39
|
result.send(:'data=', transformed_result.unwrap)
|
47
40
|
end
|
41
|
+
|
42
|
+
def contract_for(klass, result)
|
43
|
+
raise ResultTypeError.new(klass, result) unless result.is_a?(Flows::Result)
|
44
|
+
|
45
|
+
status = result.status
|
46
|
+
contracts = result.ok? ? klass.success_contracts : klass.failure_contracts
|
47
|
+
|
48
|
+
contracts[status] || raise(StatusError.new(klass, result, contracts.keys))
|
49
|
+
end
|
48
50
|
end
|
49
51
|
end
|
50
52
|
end
|