functional-light-service 0.4.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 (86) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/project-build.yml +43 -11
  3. data/.rubocop.yml +101 -160
  4. data/AUDIT-functional-light-service.md +352 -0
  5. data/Appraisals +4 -0
  6. data/CHANGELOG.md +118 -0
  7. data/Gemfile +0 -2
  8. data/README.md +1544 -1426
  9. data/Rakefile +1 -1
  10. data/VERSION +1 -1
  11. data/audit/bench.rb +99 -0
  12. data/audit/verify_findings.rb +172 -0
  13. data/functional-light-service.gemspec +15 -16
  14. data/lib/functional-light-service/action.rb +97 -101
  15. data/lib/functional-light-service/configuration.rb +26 -24
  16. data/lib/functional-light-service/context/key_verifier.rb +124 -118
  17. data/lib/functional-light-service/context.rb +63 -20
  18. data/lib/functional-light-service/deprecations.rb +26 -0
  19. data/lib/functional-light-service/errors.rb +8 -6
  20. data/lib/functional-light-service/functional/enum.rb +286 -250
  21. data/lib/functional-light-service/functional/maybe.rb +21 -15
  22. data/lib/functional-light-service/functional/monad.rb +77 -66
  23. data/lib/functional-light-service/functional/null.rb +88 -74
  24. data/lib/functional-light-service/functional/option.rb +100 -97
  25. data/lib/functional-light-service/functional/result.rb +129 -116
  26. data/lib/functional-light-service/localization_adapter.rb +48 -47
  27. data/lib/functional-light-service/organizer/execute.rb +16 -14
  28. data/lib/functional-light-service/organizer/iterate.rb +30 -25
  29. data/lib/functional-light-service/organizer/reduce_if.rb +19 -17
  30. data/lib/functional-light-service/organizer/reduce_until.rb +22 -20
  31. data/lib/functional-light-service/organizer/scoped_reducable.rb +15 -13
  32. data/lib/functional-light-service/organizer/with_callback.rb +28 -26
  33. data/lib/functional-light-service/organizer/with_reducer.rb +81 -71
  34. data/lib/functional-light-service/organizer/with_reducer_factory.rb +20 -18
  35. data/lib/functional-light-service/organizer/with_reducer_log_decorator.rb +110 -105
  36. data/lib/functional-light-service/organizer.rb +114 -104
  37. data/lib/functional-light-service/testing/context_factory.rb +48 -42
  38. data/lib/functional-light-service/testing.rb +3 -1
  39. data/lib/functional-light-service/version.rb +5 -3
  40. data/lib/functional-light-service.rb +30 -28
  41. data/spec/acceptance/after_actions_spec.rb +87 -71
  42. data/spec/acceptance/before_actions_spec.rb +115 -98
  43. data/spec/acceptance/custom_log_from_organizer_spec.rb +61 -60
  44. data/spec/acceptance/deprecation_warnings_spec.rb +82 -0
  45. data/spec/acceptance/fail_spec.rb +52 -50
  46. data/spec/acceptance/message_localization_spec.rb +119 -118
  47. data/spec/acceptance/organizer/add_aliases_spec.rb +28 -0
  48. data/spec/acceptance/organizer/add_to_context_spec.rb +30 -0
  49. data/spec/acceptance/organizer/context_failure_and_skipping_spec.rb +68 -65
  50. data/spec/acceptance/organizer/iterate_spec.rb +7 -0
  51. data/spec/acceptance/organizer/reduce_if_spec.rb +89 -83
  52. data/spec/acceptance/organizer/reduce_until_spec.rb +6 -0
  53. data/spec/acceptance/organizer/with_callback_spec.rb +113 -110
  54. data/spec/acceptance/{not_having_call_method_warning_spec.rb → organizer_entry_point_spec.rb} +10 -7
  55. data/spec/acceptance/rollback_spec.rb +183 -132
  56. data/spec/action_expects_and_promises_spec.rb +97 -93
  57. data/spec/action_promised_keys_spec.rb +126 -122
  58. data/spec/action_spec.rb +8 -0
  59. data/spec/context_spec.rb +289 -197
  60. data/spec/examples/controller_spec.rb +63 -63
  61. data/spec/examples/validate_address_spec.rb +38 -37
  62. data/spec/lib/deterministic/currify_spec.rb +90 -88
  63. data/spec/lib/deterministic/null_spec.rb +6 -1
  64. data/spec/lib/deterministic/option_spec.rb +140 -133
  65. data/spec/lib/deterministic/result/result_map_spec.rb +155 -154
  66. data/spec/lib/deterministic/result/result_shared.rb +3 -2
  67. data/spec/lib/deterministic/result_spec.rb +2 -2
  68. data/spec/lib/edge_cases_spec.rb +156 -0
  69. data/spec/lib/enum_spec.rb +1 -1
  70. data/spec/lib/native_pattern_matching_spec.rb +74 -0
  71. data/spec/organizer_spec.rb +115 -93
  72. data/spec/readme_spec.rb +45 -47
  73. data/spec/sample/calculates_order_tax_action_spec.rb +16 -16
  74. data/spec/sample/calculates_tax_spec.rb +1 -1
  75. data/spec/sample/looks_up_tax_percentage_action_spec.rb +55 -55
  76. data/spec/sample/provides_free_shipping_action_spec.rb +1 -1
  77. data/spec/sample/tax/calculates_order_tax_action.rb +10 -9
  78. data/spec/sample/tax/looks_up_tax_percentage_action.rb +28 -27
  79. data/spec/sample/tax/provides_free_shipping_action.rb +11 -10
  80. data/spec/spec_helper.rb +21 -13
  81. data/spec/test_doubles.rb +628 -564
  82. data/spec/testing/context_factory_spec.rb +21 -0
  83. metadata +49 -117
  84. data/.travis.yml +0 -24
  85. data/lib/functional-light-service/organizer/verify_call_method_exists.rb +0 -29
  86. data/spec/acceptance/include_warning_spec.rb +0 -29
@@ -1,71 +1,81 @@
1
- module FunctionalLightService
2
- module Organizer
3
- class WithReducer
4
- attr_reader :context
5
-
6
- def with(data = {})
7
- @context = FunctionalLightService::Context.make(data)
8
- self
9
- end
10
-
11
- def around_each(handler)
12
- @around_each_handler = handler
13
- self
14
- end
15
-
16
- def around_each_handler
17
- @around_each_handler ||= Class.new do
18
- def self.call(_context)
19
- yield
20
- end
21
- end
22
- end
23
-
24
- def reduce(*actions)
25
- raise "No action(s) were provided" if actions.empty?
26
-
27
- actions.flatten!
28
-
29
- actions.each_with_object(context) do |action, current_context|
30
- invoke_action(current_context, action)
31
- rescue FailWithRollbackError
32
- reduce_rollback(actions)
33
- ensure
34
- # For logging
35
- yield(current_context, action) if block_given?
36
- end
37
- end
38
-
39
- def reduce_rollback(actions)
40
- reversable_actions(actions)
41
- .reverse
42
- .reduce(context) do |context, action|
43
- if action.respond_to?(:rollback)
44
- action.rollback(context)
45
- else
46
- context
47
- end
48
- end
49
- end
50
-
51
- private
52
-
53
- def invoke_action(current_context, action)
54
- around_each_handler.call(current_context) do
55
- if action.respond_to?(:call)
56
- action.call(current_context)
57
- else
58
- action.execute(current_context)
59
- end
60
- end
61
- end
62
-
63
- def reversable_actions(actions)
64
- index_of_current_action = actions.index(@context.current_action) || 0
65
-
66
- # Reverse from the point where the fail was triggered
67
- actions.take(index_of_current_action + 1)
68
- end
69
- end
70
- end
71
- 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
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,105 +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
- @logger = logger
12
- @logged = false
13
- end
14
-
15
- def with(data = {})
16
- logger.info { "[FunctionalLightService] - calling organizer <#{organizer}>" }
17
-
18
- decorated.with(data)
19
-
20
- logger.info do
21
- "[FunctionalLightService] - keys in context: " \
22
- "#{extract_keys(decorated.context.keys)}"
23
- end
24
- self
25
- end
26
-
27
- def around_each(handler)
28
- decorated.around_each(handler)
29
- self
30
- end
31
-
32
- def reduce(*actions)
33
- decorated.reduce(*actions) do |context, action|
34
- next context if logged?
35
-
36
- if has_failure?(context)
37
- write_failure_log(context, action)
38
- next context
39
- end
40
-
41
- if skip_remaining?(context)
42
- write_skip_remaining_log(context, action)
43
- next context
44
- end
45
-
46
- write_log(action, context)
47
- end
48
- end
49
-
50
- private
51
-
52
- def write_log(action, context)
53
- return unless logger.info?
54
-
55
- logger.info("[FunctionalLightService] - executing <#{action}>")
56
- log_expects(action)
57
- log_promises(action)
58
- logger.info("[FunctionalLightService] - keys in context: "\
59
- "#{extract_keys(context.keys)}")
60
- end
61
-
62
- def log_expects(action)
63
- return unless defined?(action.expects) && action.expects.any?
64
-
65
- logger.info("[FunctionalLightService] - expects: " \
66
- "#{extract_keys(action.expects)}")
67
- end
68
-
69
- def log_promises(action)
70
- return unless defined?(action.promises) && action.promises.any?
71
-
72
- logger.info("[FunctionalLightService] - promises: " \
73
- "#{extract_keys(action.promises)}")
74
- end
75
-
76
- def extract_keys(keys)
77
- keys.map { |key| ":#{key}" }.join(', ')
78
- end
79
-
80
- def has_failure?(context)
81
- context.respond_to?(:failure?) && context.failure?
82
- end
83
-
84
- def write_failure_log(context, action)
85
- logger.warn("[FunctionalLightService] - :-((( <#{action}> has failed...")
86
- logger.warn("[FunctionalLightService] - context message: #{context.message}")
87
- @logged = true
88
- end
89
-
90
- def skip_remaining?(context)
91
- context.respond_to?(:skip_remaining?) && context.skip_remaining?
92
- end
93
-
94
- def write_skip_remaining_log(context, action)
95
- return unless logger.info?
96
-
97
- msg = "[FunctionalLightService] - ;-) <#{action}> has decided " \
98
- "to skip the rest of the actions"
99
- logger.info(msg)
100
- logger.info("[FunctionalLightService] - context message: #{context.message}")
101
- @logged = true
102
- end
103
- end
104
- end
105
- 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,104 +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
- end
67
-
68
- module Macros
69
- def aliases(key_hash)
70
- @aliases = key_hash
71
- end
72
-
73
- # This looks like an accessor,
74
- # but it's used as a macro in the Organizer
75
- def before_actions(*logic)
76
- self.before_actions = logic
77
- end
78
-
79
- def before_actions=(logic)
80
- @before_actions = [logic].flatten
81
- end
82
-
83
- def append_before_actions(action)
84
- @before_actions ||= []
85
- @before_actions.push(action)
86
- end
87
-
88
- # This looks like an accessor,
89
- # but it's used as a macro in the Organizer
90
- def after_actions(*logic)
91
- self.after_actions = logic
92
- end
93
-
94
- def after_actions=(logic)
95
- @after_actions = [logic].flatten
96
- end
97
-
98
- def append_after_actions(action)
99
- @after_actions ||= []
100
- @after_actions.push(action)
101
- end
102
- end
103
- end
104
- 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