functional-light-service 0.5.4 → 6.1.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/.github/workflows/project-build.yml +35 -11
- data/.rubocop.yml +101 -160
- data/AUDIT-functional-light-service.md +352 -0
- data/CHANGELOG.md +45 -0
- data/README.md +88 -2
- data/audit/bench.rb +99 -0
- data/audit/verify_findings.rb +172 -0
- data/functional-light-service.gemspec +15 -21
- data/lib/functional-light-service/action.rb +97 -101
- data/lib/functional-light-service/configuration.rb +26 -24
- data/lib/functional-light-service/context/key_verifier.rb +124 -118
- data/lib/functional-light-service/context.rb +63 -20
- data/lib/functional-light-service/deprecations.rb +26 -0
- data/lib/functional-light-service/errors.rb +8 -6
- data/lib/functional-light-service/functional/enum.rb +286 -250
- data/lib/functional-light-service/functional/maybe.rb +21 -15
- data/lib/functional-light-service/functional/monad.rb +77 -66
- data/lib/functional-light-service/functional/null.rb +88 -74
- data/lib/functional-light-service/functional/option.rb +100 -97
- data/lib/functional-light-service/functional/result.rb +129 -116
- data/lib/functional-light-service/functional/sequencer.rb +144 -0
- data/lib/functional-light-service/localization_adapter.rb +48 -47
- data/lib/functional-light-service/organizer/execute.rb +16 -14
- data/lib/functional-light-service/organizer/iterate.rb +30 -25
- data/lib/functional-light-service/organizer/reduce_if.rb +19 -17
- data/lib/functional-light-service/organizer/reduce_until.rb +22 -20
- data/lib/functional-light-service/organizer/scoped_reducable.rb +15 -13
- data/lib/functional-light-service/organizer/with_callback.rb +28 -26
- data/lib/functional-light-service/organizer/with_reducer.rb +81 -77
- data/lib/functional-light-service/organizer/with_reducer_factory.rb +20 -18
- data/lib/functional-light-service/organizer/with_reducer_log_decorator.rb +110 -108
- data/lib/functional-light-service/organizer.rb +114 -114
- data/lib/functional-light-service/testing/context_factory.rb +48 -42
- data/lib/functional-light-service/testing.rb +3 -1
- data/lib/functional-light-service/version.rb +5 -3
- data/lib/functional-light-service.rb +31 -28
- data/spec/acceptance/after_actions_spec.rb +87 -71
- data/spec/acceptance/before_actions_spec.rb +115 -98
- data/spec/acceptance/custom_log_from_organizer_spec.rb +61 -60
- data/spec/acceptance/deprecation_warnings_spec.rb +82 -0
- data/spec/acceptance/fail_spec.rb +52 -50
- data/spec/acceptance/message_localization_spec.rb +119 -118
- data/spec/acceptance/organizer/context_failure_and_skipping_spec.rb +68 -65
- data/spec/acceptance/organizer/reduce_if_spec.rb +89 -89
- data/spec/acceptance/organizer/with_callback_spec.rb +113 -110
- data/spec/acceptance/{not_having_call_method_warning_spec.rb → organizer_entry_point_spec.rb} +10 -7
- data/spec/acceptance/rollback_spec.rb +183 -132
- data/spec/action_expects_and_promises_spec.rb +97 -93
- data/spec/action_promised_keys_spec.rb +126 -122
- data/spec/context_spec.rb +289 -197
- data/spec/examples/controller_spec.rb +63 -63
- data/spec/examples/validate_address_spec.rb +38 -37
- data/spec/lib/deterministic/currify_spec.rb +90 -88
- data/spec/lib/deterministic/null_spec.rb +6 -1
- data/spec/lib/deterministic/option_spec.rb +140 -137
- data/spec/lib/deterministic/result/result_map_spec.rb +155 -154
- data/spec/lib/deterministic/result/result_shared.rb +3 -2
- data/spec/lib/deterministic/result_spec.rb +2 -2
- data/spec/lib/deterministic/sequencer_spec.rb +506 -0
- data/spec/lib/edge_cases_spec.rb +156 -0
- data/spec/lib/enum_spec.rb +1 -1
- data/spec/lib/native_pattern_matching_spec.rb +74 -0
- data/spec/organizer_spec.rb +115 -114
- data/spec/readme_spec.rb +45 -47
- data/spec/sample/calculates_order_tax_action_spec.rb +16 -16
- data/spec/sample/calculates_tax_spec.rb +1 -1
- data/spec/sample/looks_up_tax_percentage_action_spec.rb +55 -55
- data/spec/sample/tax/calculates_order_tax_action.rb +10 -9
- data/spec/sample/tax/looks_up_tax_percentage_action.rb +28 -27
- data/spec/sample/tax/provides_free_shipping_action.rb +11 -10
- data/spec/spec_helper.rb +6 -0
- data/spec/test_doubles.rb +628 -599
- data/spec/testing/context_factory_spec.rb +21 -0
- metadata +48 -162
- data/lib/functional-light-service/organizer/verify_call_method_exists.rb +0 -29
- data/spec/acceptance/include_warning_spec.rb +0 -29
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
ctx.reset_skip_remaining! unless ctx.failure?
|
|
8
|
-
|
|
9
|
-
ctx
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
end
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FunctionalLightService
|
|
4
|
+
module Organizer
|
|
5
|
+
module ScopedReducable
|
|
6
|
+
def scoped_reduce(organizer, ctx, steps)
|
|
7
|
+
ctx.reset_skip_remaining! unless ctx.failure?
|
|
8
|
+
ctx = organizer.with(ctx).reduce([steps])
|
|
9
|
+
ctx.reset_skip_remaining! unless ctx.failure?
|
|
10
|
+
|
|
11
|
+
ctx
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -1,26 +1,28 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
ctx[:callback]
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
ctx
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
end
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FunctionalLightService
|
|
4
|
+
module Organizer
|
|
5
|
+
class WithCallback
|
|
6
|
+
extend ScopedReducable
|
|
7
|
+
|
|
8
|
+
def self.run(organizer, action, steps)
|
|
9
|
+
->(ctx) do
|
|
10
|
+
return ctx if ctx.stop_processing?
|
|
11
|
+
|
|
12
|
+
# This will only allow 2 level deep nesting of callbacks
|
|
13
|
+
previous_callback = ctx[:callback]
|
|
14
|
+
|
|
15
|
+
ctx[:callback] = ->(context) do
|
|
16
|
+
ctx = scoped_reduce(organizer, context, steps)
|
|
17
|
+
ctx
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
ctx = action.execute(ctx)
|
|
21
|
+
ctx[:callback] = previous_callback
|
|
22
|
+
|
|
23
|
+
ctx
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -1,77 +1,81 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
actions.
|
|
34
|
-
|
|
35
|
-
actions.
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
context
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
action.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
#
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FunctionalLightService
|
|
4
|
+
module Organizer
|
|
5
|
+
class WithReducer
|
|
6
|
+
attr_reader :context
|
|
7
|
+
attr_accessor :organizer
|
|
8
|
+
|
|
9
|
+
def initialize(monitored_organizer = nil)
|
|
10
|
+
@organizer = monitored_organizer
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def with(data = {})
|
|
14
|
+
@context = FunctionalLightService::Context.make(data)
|
|
15
|
+
@context.organized_by = organizer
|
|
16
|
+
self
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Handler di default condiviso: prima veniva creata una classe anonima
|
|
20
|
+
# per ogni WithReducer senza around_each
|
|
21
|
+
NOOP_AROUND_EACH_HANDLER = ->(_context, &block) { block.call }
|
|
22
|
+
|
|
23
|
+
def around_each(handler)
|
|
24
|
+
@around_each_handler = handler
|
|
25
|
+
self
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def around_each_handler
|
|
29
|
+
@around_each_handler || NOOP_AROUND_EACH_HANDLER
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def reduce(*actions)
|
|
33
|
+
raise "No action(s) were provided" if actions.empty?
|
|
34
|
+
|
|
35
|
+
actions.flatten!
|
|
36
|
+
|
|
37
|
+
actions.each_with_index.with_object(context) do |(action, index), current_context|
|
|
38
|
+
invoke_action(current_context, action)
|
|
39
|
+
rescue FailWithRollbackError
|
|
40
|
+
reduce_rollback(actions, index)
|
|
41
|
+
ensure
|
|
42
|
+
# For logging
|
|
43
|
+
yield(current_context, action) if block_given?
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def reduce_rollback(actions, index_of_failed_action = nil)
|
|
48
|
+
reversable_actions(actions, index_of_failed_action)
|
|
49
|
+
.reverse
|
|
50
|
+
.reduce(context) do |context, action|
|
|
51
|
+
if action.respond_to?(:rollback)
|
|
52
|
+
action.rollback(context)
|
|
53
|
+
else
|
|
54
|
+
context
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def invoke_action(current_context, action)
|
|
62
|
+
around_each_handler.call(current_context) do
|
|
63
|
+
if action.respond_to?(:call)
|
|
64
|
+
action.call(current_context)
|
|
65
|
+
else
|
|
66
|
+
action.execute(current_context)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def reversable_actions(actions, index_of_failed_action = nil)
|
|
72
|
+
# L'indice viene tracciato nel reduce: actions.index troverebbe la prima
|
|
73
|
+
# occorrenza e con azioni duplicate il rollback sarebbe parziale
|
|
74
|
+
index_of_failed_action ||= actions.index(@context.current_action) || 0
|
|
75
|
+
|
|
76
|
+
# Reverse from the point where the fail was triggered
|
|
77
|
+
actions.take(index_of_failed_action + 1)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -1,18 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
end
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FunctionalLightService
|
|
4
|
+
module Organizer
|
|
5
|
+
class WithReducerFactory
|
|
6
|
+
def self.make(monitored_organizer)
|
|
7
|
+
logger = monitored_organizer.logger || FunctionalLightService::Configuration.logger
|
|
8
|
+
decorated = WithReducer.new(monitored_organizer)
|
|
9
|
+
|
|
10
|
+
return decorated if logger.nil?
|
|
11
|
+
|
|
12
|
+
WithReducerLogDecorator.new(
|
|
13
|
+
monitored_organizer,
|
|
14
|
+
:decorated => decorated,
|
|
15
|
+
:logger => logger
|
|
16
|
+
)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -1,108 +1,110 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
logger.info
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
end
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FunctionalLightService
|
|
4
|
+
module Organizer
|
|
5
|
+
class WithReducerLogDecorator
|
|
6
|
+
attr_reader :logged, :logger, :decorated, :organizer
|
|
7
|
+
|
|
8
|
+
alias logged? logged
|
|
9
|
+
|
|
10
|
+
def initialize(organizer, logger:, decorated: WithReducer.new)
|
|
11
|
+
@decorated = decorated
|
|
12
|
+
@organizer = organizer
|
|
13
|
+
|
|
14
|
+
decorated.organizer = organizer
|
|
15
|
+
|
|
16
|
+
@logger = logger
|
|
17
|
+
@logged = false
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def with(data = {})
|
|
21
|
+
logger.info { "[FunctionalLightService] - calling organizer <#{organizer}>" }
|
|
22
|
+
|
|
23
|
+
decorated.with(data)
|
|
24
|
+
|
|
25
|
+
logger.info do
|
|
26
|
+
"[FunctionalLightService] - keys in context: " \
|
|
27
|
+
"#{extract_keys(decorated.context.keys)}"
|
|
28
|
+
end
|
|
29
|
+
self
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def around_each(handler)
|
|
33
|
+
decorated.around_each(handler)
|
|
34
|
+
self
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def reduce(*actions)
|
|
38
|
+
decorated.reduce(*actions) do |context, action|
|
|
39
|
+
next context if logged?
|
|
40
|
+
|
|
41
|
+
if has_failure?(context)
|
|
42
|
+
write_failure_log(context, action)
|
|
43
|
+
next context
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
if skip_remaining?(context)
|
|
47
|
+
write_skip_remaining_log(context, action)
|
|
48
|
+
next context
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
write_log(action, context)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def write_log(action, context)
|
|
58
|
+
return unless logger.info?
|
|
59
|
+
|
|
60
|
+
logger.info("[FunctionalLightService] - executing <#{action}>")
|
|
61
|
+
log_expects(action)
|
|
62
|
+
log_promises(action)
|
|
63
|
+
logger.info("[FunctionalLightService] - keys in context: "\
|
|
64
|
+
"#{extract_keys(context.keys)}")
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def log_expects(action)
|
|
68
|
+
return unless defined?(action.expects) && action.expects.any?
|
|
69
|
+
|
|
70
|
+
logger.info("[FunctionalLightService] - expects: " \
|
|
71
|
+
"#{extract_keys(action.expects)}")
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def log_promises(action)
|
|
75
|
+
return unless defined?(action.promises) && action.promises.any?
|
|
76
|
+
|
|
77
|
+
logger.info("[FunctionalLightService] - promises: " \
|
|
78
|
+
"#{extract_keys(action.promises)}")
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def extract_keys(keys)
|
|
82
|
+
keys.map { |key| ":#{key}" }.join(', ')
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def has_failure?(context)
|
|
86
|
+
context.respond_to?(:failure?) && context.failure?
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def write_failure_log(context, action)
|
|
90
|
+
logger.warn("[FunctionalLightService] - :-((( <#{action}> has failed...")
|
|
91
|
+
logger.warn("[FunctionalLightService] - context message: #{context.message}")
|
|
92
|
+
@logged = true
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def skip_remaining?(context)
|
|
96
|
+
context.respond_to?(:skip_remaining?) && context.skip_remaining?
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def write_skip_remaining_log(context, action)
|
|
100
|
+
return unless logger.info?
|
|
101
|
+
|
|
102
|
+
msg = "[FunctionalLightService] - ;-) <#{action}> has decided " \
|
|
103
|
+
"to skip the rest of the actions"
|
|
104
|
+
logger.info(msg)
|
|
105
|
+
logger.info("[FunctionalLightService] - context message: #{context.message}")
|
|
106
|
+
@logged = true
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|