flows 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
[![Build Status](https://github.com/ffloyd/flows/workflows/
|
3
|
+
[![Build Status](https://github.com/ffloyd/flows/workflows/Test/badge.svg)](https://github.com/ffloyd/flows/actions)
|
4
4
|
[![codecov](https://codecov.io/gh/ffloyd/flows/branch/master/graph/badge.svg)](https://codecov.io/gh/ffloyd/flows)
|
5
5
|
[![Gem Version](https://badge.fury.io/rb/flows.svg)](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
|
-
[![Build Status](https://github.com/ffloyd/flows/workflows/
|
3
|
+
[![Build Status](https://github.com/ffloyd/flows/workflows/Test/badge.svg)](https://github.com/ffloyd/flows/actions)
|
4
4
|
[![codecov](https://codecov.io/gh/ffloyd/flows/branch/master/graph/badge.svg)](https://codecov.io/gh/ffloyd/flows)
|
5
5
|
[![Gem Version](https://badge.fury.io/rb/flows.svg)](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
|