functional-light-service 0.5.4 → 6.0.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 (75) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/project-build.yml +35 -11
  3. data/.rubocop.yml +101 -160
  4. data/AUDIT-functional-light-service.md +352 -0
  5. data/CHANGELOG.md +38 -0
  6. data/README.md +54 -2
  7. data/audit/bench.rb +99 -0
  8. data/audit/verify_findings.rb +172 -0
  9. data/functional-light-service.gemspec +15 -21
  10. data/lib/functional-light-service/action.rb +97 -101
  11. data/lib/functional-light-service/configuration.rb +26 -24
  12. data/lib/functional-light-service/context/key_verifier.rb +124 -118
  13. data/lib/functional-light-service/context.rb +63 -20
  14. data/lib/functional-light-service/deprecations.rb +26 -0
  15. data/lib/functional-light-service/errors.rb +8 -6
  16. data/lib/functional-light-service/functional/enum.rb +286 -250
  17. data/lib/functional-light-service/functional/maybe.rb +21 -15
  18. data/lib/functional-light-service/functional/monad.rb +77 -66
  19. data/lib/functional-light-service/functional/null.rb +88 -74
  20. data/lib/functional-light-service/functional/option.rb +100 -97
  21. data/lib/functional-light-service/functional/result.rb +129 -116
  22. data/lib/functional-light-service/localization_adapter.rb +48 -47
  23. data/lib/functional-light-service/organizer/execute.rb +16 -14
  24. data/lib/functional-light-service/organizer/iterate.rb +30 -25
  25. data/lib/functional-light-service/organizer/reduce_if.rb +19 -17
  26. data/lib/functional-light-service/organizer/reduce_until.rb +22 -20
  27. data/lib/functional-light-service/organizer/scoped_reducable.rb +15 -13
  28. data/lib/functional-light-service/organizer/with_callback.rb +28 -26
  29. data/lib/functional-light-service/organizer/with_reducer.rb +81 -77
  30. data/lib/functional-light-service/organizer/with_reducer_factory.rb +20 -18
  31. data/lib/functional-light-service/organizer/with_reducer_log_decorator.rb +110 -108
  32. data/lib/functional-light-service/organizer.rb +114 -114
  33. data/lib/functional-light-service/testing/context_factory.rb +48 -42
  34. data/lib/functional-light-service/testing.rb +3 -1
  35. data/lib/functional-light-service/version.rb +5 -3
  36. data/lib/functional-light-service.rb +30 -28
  37. data/spec/acceptance/after_actions_spec.rb +87 -71
  38. data/spec/acceptance/before_actions_spec.rb +115 -98
  39. data/spec/acceptance/custom_log_from_organizer_spec.rb +61 -60
  40. data/spec/acceptance/deprecation_warnings_spec.rb +82 -0
  41. data/spec/acceptance/fail_spec.rb +52 -50
  42. data/spec/acceptance/message_localization_spec.rb +119 -118
  43. data/spec/acceptance/organizer/context_failure_and_skipping_spec.rb +68 -65
  44. data/spec/acceptance/organizer/reduce_if_spec.rb +89 -89
  45. data/spec/acceptance/organizer/with_callback_spec.rb +113 -110
  46. data/spec/acceptance/{not_having_call_method_warning_spec.rb → organizer_entry_point_spec.rb} +10 -7
  47. data/spec/acceptance/rollback_spec.rb +183 -132
  48. data/spec/action_expects_and_promises_spec.rb +97 -93
  49. data/spec/action_promised_keys_spec.rb +126 -122
  50. data/spec/context_spec.rb +289 -197
  51. data/spec/examples/controller_spec.rb +63 -63
  52. data/spec/examples/validate_address_spec.rb +38 -37
  53. data/spec/lib/deterministic/currify_spec.rb +90 -88
  54. data/spec/lib/deterministic/null_spec.rb +6 -1
  55. data/spec/lib/deterministic/option_spec.rb +140 -137
  56. data/spec/lib/deterministic/result/result_map_spec.rb +155 -154
  57. data/spec/lib/deterministic/result/result_shared.rb +3 -2
  58. data/spec/lib/deterministic/result_spec.rb +2 -2
  59. data/spec/lib/edge_cases_spec.rb +156 -0
  60. data/spec/lib/enum_spec.rb +1 -1
  61. data/spec/lib/native_pattern_matching_spec.rb +74 -0
  62. data/spec/organizer_spec.rb +115 -114
  63. data/spec/readme_spec.rb +45 -47
  64. data/spec/sample/calculates_order_tax_action_spec.rb +16 -16
  65. data/spec/sample/calculates_tax_spec.rb +1 -1
  66. data/spec/sample/looks_up_tax_percentage_action_spec.rb +55 -55
  67. data/spec/sample/tax/calculates_order_tax_action.rb +10 -9
  68. data/spec/sample/tax/looks_up_tax_percentage_action.rb +28 -27
  69. data/spec/sample/tax/provides_free_shipping_action.rb +11 -10
  70. data/spec/spec_helper.rb +6 -0
  71. data/spec/test_doubles.rb +628 -599
  72. data/spec/testing/context_factory_spec.rb +21 -0
  73. metadata +45 -161
  74. data/lib/functional-light-service/organizer/verify_call_method_exists.rb +0 -29
  75. data/spec/acceptance/include_warning_spec.rb +0 -29
@@ -1,77 +1,81 @@
1
- module FunctionalLightService
2
- module Organizer
3
- class WithReducer
4
- attr_reader :context
5
- attr_accessor :organizer
6
-
7
- def initialize(monitored_organizer = nil)
8
- @organizer = monitored_organizer
9
- end
10
-
11
- def with(data = {})
12
- @context = FunctionalLightService::Context.make(data)
13
- @context.organized_by = organizer
14
- self
15
- end
16
-
17
- def around_each(handler)
18
- @around_each_handler = handler
19
- self
20
- end
21
-
22
- def around_each_handler
23
- @around_each_handler ||= Class.new do
24
- def self.call(_context)
25
- yield
26
- end
27
- end
28
- end
29
-
30
- def reduce(*actions)
31
- raise "No action(s) were provided" if actions.empty?
32
-
33
- actions.flatten!
34
-
35
- actions.each_with_object(context) do |action, current_context|
36
- invoke_action(current_context, action)
37
- rescue FailWithRollbackError
38
- reduce_rollback(actions)
39
- ensure
40
- # For logging
41
- yield(current_context, action) if block_given?
42
- end
43
- end
44
-
45
- def reduce_rollback(actions)
46
- reversable_actions(actions)
47
- .reverse
48
- .reduce(context) do |context, action|
49
- if action.respond_to?(:rollback)
50
- action.rollback(context)
51
- else
52
- context
53
- end
54
- end
55
- end
56
-
57
- private
58
-
59
- def invoke_action(current_context, action)
60
- around_each_handler.call(current_context) do
61
- if action.respond_to?(:call)
62
- action.call(current_context)
63
- else
64
- action.execute(current_context)
65
- end
66
- end
67
- end
68
-
69
- def reversable_actions(actions)
70
- index_of_current_action = actions.index(@context.current_action) || 0
71
-
72
- # Reverse from the point where the fail was triggered
73
- actions.take(index_of_current_action + 1)
74
- end
75
- end
76
- end
77
- end
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
- module FunctionalLightService
2
- module Organizer
3
- class WithReducerFactory
4
- def self.make(monitored_organizer)
5
- logger = monitored_organizer.logger || FunctionalLightService::Configuration.logger
6
- decorated = WithReducer.new(monitored_organizer)
7
-
8
- return decorated if logger.nil?
9
-
10
- WithReducerLogDecorator.new(
11
- monitored_organizer,
12
- :decorated => decorated,
13
- :logger => logger
14
- )
15
- end
16
- end
17
- end
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
- module FunctionalLightService
2
- module Organizer
3
- class WithReducerLogDecorator
4
- attr_reader :logged, :logger, :decorated, :organizer
5
-
6
- alias logged? logged
7
-
8
- def initialize(organizer, logger:, decorated: WithReducer.new)
9
- @decorated = decorated
10
- @organizer = organizer
11
-
12
- decorated.organizer = organizer
13
-
14
- @logger = logger
15
- @logged = false
16
- end
17
-
18
- def with(data = {})
19
- logger.info { "[FunctionalLightService] - calling organizer <#{organizer}>" }
20
-
21
- decorated.with(data)
22
-
23
- logger.info do
24
- "[FunctionalLightService] - keys in context: " \
25
- "#{extract_keys(decorated.context.keys)}"
26
- end
27
- self
28
- end
29
-
30
- def around_each(handler)
31
- decorated.around_each(handler)
32
- self
33
- end
34
-
35
- def reduce(*actions)
36
- decorated.reduce(*actions) do |context, action|
37
- next context if logged?
38
-
39
- if has_failure?(context)
40
- write_failure_log(context, action)
41
- next context
42
- end
43
-
44
- if skip_remaining?(context)
45
- write_skip_remaining_log(context, action)
46
- next context
47
- end
48
-
49
- write_log(action, context)
50
- end
51
- end
52
-
53
- private
54
-
55
- def write_log(action, context)
56
- return unless logger.info?
57
-
58
- logger.info("[FunctionalLightService] - executing <#{action}>")
59
- log_expects(action)
60
- log_promises(action)
61
- logger.info("[FunctionalLightService] - keys in context: "\
62
- "#{extract_keys(context.keys)}")
63
- end
64
-
65
- def log_expects(action)
66
- return unless defined?(action.expects) && action.expects.any?
67
-
68
- logger.info("[FunctionalLightService] - expects: " \
69
- "#{extract_keys(action.expects)}")
70
- end
71
-
72
- def log_promises(action)
73
- return unless defined?(action.promises) && action.promises.any?
74
-
75
- logger.info("[FunctionalLightService] - promises: " \
76
- "#{extract_keys(action.promises)}")
77
- end
78
-
79
- def extract_keys(keys)
80
- keys.map { |key| ":#{key}" }.join(', ')
81
- end
82
-
83
- def has_failure?(context)
84
- context.respond_to?(:failure?) && context.failure?
85
- end
86
-
87
- def write_failure_log(context, action)
88
- logger.warn("[FunctionalLightService] - :-((( <#{action}> has failed...")
89
- logger.warn("[FunctionalLightService] - context message: #{context.message}")
90
- @logged = true
91
- end
92
-
93
- def skip_remaining?(context)
94
- context.respond_to?(:skip_remaining?) && context.skip_remaining?
95
- end
96
-
97
- def write_skip_remaining_log(context, action)
98
- return unless logger.info?
99
-
100
- msg = "[FunctionalLightService] - ;-) <#{action}> has decided " \
101
- "to skip the rest of the actions"
102
- logger.info(msg)
103
- logger.info("[FunctionalLightService] - context message: #{context.message}")
104
- @logged = true
105
- end
106
- end
107
- end
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
@@ -1,114 +1,114 @@
1
- module FunctionalLightService
2
- module Organizer
3
- def self.extended(base_class)
4
- base_class.extend ClassMethods
5
- base_class.extend Macros
6
- end
7
-
8
- def self.included(base_class)
9
- msg = "DEPRECATION WARNING:\n" \
10
- "Including FunctionalLightService::Organizer is deprecated\n" \
11
- "Please use `extend FunctionalLightService::Organizer` instead"
12
- print msg
13
- extended(base_class)
14
- end
15
-
16
- # In case this module is included
17
- module ClassMethods
18
- def with(data = {})
19
- VerifyCallMethodExists.run(self, caller(1..1).first)
20
- data[:_aliases] = @aliases if @aliases
21
-
22
- if @before_actions
23
- data[:_before_actions] = @before_actions.dup
24
- @before_actions = nil
25
- end
26
-
27
- if @after_actions
28
- data[:_after_actions] = @after_actions.dup
29
- @after_actions = nil
30
- end
31
-
32
- WithReducerFactory.make(self).with(data)
33
- end
34
-
35
- def reduce(*actions)
36
- with({}).reduce(actions)
37
- end
38
-
39
- def reduce_if(condition_block, steps)
40
- ReduceIf.run(self, condition_block, steps)
41
- end
42
-
43
- def reduce_until(condition_block, steps)
44
- ReduceUntil.run(self, condition_block, steps)
45
- end
46
-
47
- def iterate(collection_key, steps)
48
- Iterate.run(self, collection_key, steps)
49
- end
50
-
51
- def execute(code_block)
52
- Execute.run(code_block)
53
- end
54
-
55
- def with_callback(action, steps)
56
- WithCallback.run(self, action, steps)
57
- end
58
-
59
- def log_with(logger)
60
- @logger = logger
61
- end
62
-
63
- def logger
64
- @logger
65
- end
66
-
67
- def add_to_context(**args)
68
- args.map do |key, value|
69
- execute(->(ctx) { ctx[key.to_sym] = value })
70
- end
71
- end
72
-
73
- def add_aliases(args)
74
- execute(->(ctx) { ctx.assign_aliases(ctx.aliases.merge(args)) })
75
- end
76
- end
77
-
78
- module Macros
79
- def aliases(key_hash)
80
- @aliases = key_hash
81
- end
82
-
83
- # This looks like an accessor,
84
- # but it's used as a macro in the Organizer
85
- def before_actions(*logic)
86
- self.before_actions = logic
87
- end
88
-
89
- def before_actions=(logic)
90
- @before_actions = [logic].flatten
91
- end
92
-
93
- def append_before_actions(action)
94
- @before_actions ||= []
95
- @before_actions.push(action)
96
- end
97
-
98
- # This looks like an accessor,
99
- # but it's used as a macro in the Organizer
100
- def after_actions(*logic)
101
- self.after_actions = logic
102
- end
103
-
104
- def after_actions=(logic)
105
- @after_actions = [logic].flatten
106
- end
107
-
108
- def append_after_actions(action)
109
- @after_actions ||= []
110
- @after_actions.push(action)
111
- end
112
- end
113
- end
114
- end
1
+ # frozen_string_literal: true
2
+
3
+ module FunctionalLightService
4
+ module Organizer
5
+ def self.extended(base_class)
6
+ base_class.extend ClassMethods
7
+ base_class.extend Macros
8
+ end
9
+
10
+ def self.included(base_class)
11
+ FunctionalLightService::Deprecations.warn(
12
+ "Including FunctionalLightService::Organizer is deprecated; " \
13
+ "use `extend FunctionalLightService::Organizer` instead"
14
+ )
15
+ extended(base_class)
16
+ end
17
+
18
+ # In case this module is included
19
+ module ClassMethods
20
+ def with(data = {})
21
+ data[:_aliases] = @aliases if @aliases
22
+
23
+ # Gli hook di classe vengono solo letti (mai azzerati): devono valere
24
+ # per ogni chiamata, anche concorrente
25
+ data[:_before_actions] = @before_actions.dup if @before_actions
26
+ data[:_after_actions] = @after_actions.dup if @after_actions
27
+
28
+ WithReducerFactory.make(self).with(data)
29
+ end
30
+
31
+ def reduce(*actions)
32
+ with({}).reduce(actions)
33
+ end
34
+
35
+ def reduce_if(condition_block, steps)
36
+ ReduceIf.run(self, condition_block, steps)
37
+ end
38
+
39
+ def reduce_until(condition_block, steps)
40
+ ReduceUntil.run(self, condition_block, steps)
41
+ end
42
+
43
+ def iterate(collection_key, steps)
44
+ Iterate.run(self, collection_key, steps)
45
+ end
46
+
47
+ def execute(code_block)
48
+ Execute.run(code_block)
49
+ end
50
+
51
+ def with_callback(action, steps)
52
+ WithCallback.run(self, action, steps)
53
+ end
54
+
55
+ def log_with(logger)
56
+ @logger = logger
57
+ end
58
+
59
+ def logger
60
+ @logger
61
+ end
62
+
63
+ def add_to_context(**args)
64
+ args.map do |key, value|
65
+ execute(->(ctx) { ctx[key.to_sym] = value })
66
+ end
67
+ end
68
+
69
+ def add_aliases(args)
70
+ execute(->(ctx) { ctx.assign_aliases(ctx.aliases.merge(args)) })
71
+ end
72
+ end
73
+
74
+ module Macros
75
+ def aliases(key_hash)
76
+ @aliases = key_hash
77
+ end
78
+
79
+ # This looks like an accessor,
80
+ # but it's used as a macro in the Organizer
81
+ def before_actions(*logic)
82
+ self.before_actions = logic
83
+ end
84
+
85
+ def before_actions=(logic)
86
+ @before_actions = logic.nil? ? nil : [logic].flatten
87
+ end
88
+
89
+ def append_before_actions(action)
90
+ @before_actions ||= []
91
+ @before_actions.push(action)
92
+ end
93
+
94
+ def remove_before_actions(action)
95
+ @before_actions&.delete(action)
96
+ end
97
+
98
+ # This looks like an accessor,
99
+ # but it's used as a macro in the Organizer
100
+ def after_actions(*logic)
101
+ self.after_actions = logic
102
+ end
103
+
104
+ def after_actions=(logic)
105
+ @after_actions = logic.nil? ? nil : [logic].flatten
106
+ end
107
+
108
+ def append_after_actions(action)
109
+ @after_actions ||= []
110
+ @after_actions.push(action)
111
+ end
112
+ end
113
+ end
114
+ end