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,116 +1,129 @@
1
- module FunctionalLightService
2
- Result = FunctionalLightService.enum do
3
- Success(:s)
4
- Failure(:f)
5
- end
6
-
7
- class Result
8
- class << self
9
- def try!
10
- Success.new(yield)
11
- rescue StandardError => e
12
- Failure.new(e)
13
- end
14
- end
15
- end
16
-
17
- # rubocop:disable Metrics/BlockLength
18
- FunctionalLightService.impl(Result) do
19
- def map(proc = nil, &block)
20
- success? ? bind(proc || block) : self
21
- end
22
-
23
- alias :>> :map
24
- alias :and_then :map
25
-
26
- def map_err(proc = nil, &block)
27
- failure? ? bind(proc || block) : self
28
- end
29
-
30
- alias :or_else :map_err
31
-
32
- def pipe(proc = nil, &block)
33
- (proc || block).call(self)
34
- self
35
- end
36
-
37
- alias :<< :pipe
38
-
39
- def success?
40
- is_a? Result::Success
41
- end
42
-
43
- def failure?
44
- is_a? Result::Failure
45
- end
46
-
47
- def or(other)
48
- unless other.is_a? Result
49
- msg = "Expected #{other.inspect} to be a Result"
50
- raise FunctionalLightService::Monad::NotMonadError, msg
51
- end
52
-
53
- match do
54
- Success() { |_| self }
55
- Failure() { |_| other }
56
- end
57
- end
58
-
59
- def and(other)
60
- unless other.is_a? Result
61
- msg = "Expected #{other.inspect} to be a Result"
62
- raise FunctionalLightService::Monad::NotMonadError, msg
63
- end
64
-
65
- match do
66
- Success() { |_| other }
67
- Failure() { |_| self }
68
- end
69
- end
70
-
71
- def +(other)
72
- unless other.is_a? Result
73
- msg = "Expected #{other.inspect} to be a Result"
74
- raise FunctionalLightService::Monad::NotMonadError, msg
75
- end
76
-
77
- match do
78
- Success(where { other.success? }) { |s| Result::Success.new(s + other.value) }
79
- Failure(where { other.failure? }) { |f| Result::Failure.new(f + other.value) }
80
- Success() { |_| other } # implied other.failure?
81
- Failure() { |_| self } # implied other.success?
82
- end
83
- end
84
-
85
- def try(proc = nil, &block)
86
- map(proc, &block)
87
- rescue StandardError => e
88
- Result::Failure.new(e)
89
- end
90
-
91
- alias :>= :try
92
- end
93
- # rubocop:enable Metrics/BlockLength
94
- end
95
-
96
- module FunctionalLightService
97
- module Prelude
98
- module Result
99
- # rubocop:disable Naming/MethodName
100
- def try!(&block)
101
- FunctionalLightService::Result.try!(&block)
102
- end
103
-
104
- def Success(s)
105
- FunctionalLightService::Result::Success.new(s)
106
- end
107
-
108
- def Failure(f)
109
- FunctionalLightService::Result::Failure.new(f)
110
- end
111
- # rubocop:enable Naming/MethodName
112
- end
113
-
114
- include Result
115
- end
116
- end
1
+ # frozen_string_literal: true
2
+
3
+ module FunctionalLightService
4
+ Result = FunctionalLightService.enum do
5
+ Success(:s)
6
+ Failure(:f)
7
+ end
8
+
9
+ class Result
10
+ class << self
11
+ def try!
12
+ Success.new(yield)
13
+ rescue StandardError => e
14
+ Failure.new(e)
15
+ end
16
+ end
17
+ end
18
+
19
+ # rubocop:disable Metrics/BlockLength
20
+ FunctionalLightService.impl(Result) do
21
+ def map(proc = nil, &block)
22
+ success? ? bind(proc || block) : self
23
+ end
24
+
25
+ alias :>> :map
26
+ alias :and_then :map
27
+
28
+ def map_err(proc = nil, &block)
29
+ failure? ? bind(proc || block) : self
30
+ end
31
+
32
+ alias :or_else :map_err
33
+
34
+ def pipe(proc = nil, &block)
35
+ (proc || block).call(self)
36
+ self
37
+ end
38
+
39
+ def <<(proc = nil, &block)
40
+ FunctionalLightService::Deprecations.warn(
41
+ "Result#<< is deprecated; use #pipe instead"
42
+ )
43
+ pipe(proc, &block)
44
+ end
45
+
46
+ def success?
47
+ is_a? Result::Success
48
+ end
49
+
50
+ def failure?
51
+ is_a? Result::Failure
52
+ end
53
+
54
+ # Le operazioni usano il dispatch diretto invece del motore match:
55
+ # stessa semantica, ~2 ordini di grandezza piu veloce (audit, finding 3.1)
56
+ def or(other)
57
+ unless other.is_a? Result
58
+ msg = "Expected #{other.inspect} to be a Result"
59
+ raise FunctionalLightService::Monad::NotMonadError, msg
60
+ end
61
+
62
+ success? ? self : other
63
+ end
64
+
65
+ def and(other)
66
+ unless other.is_a? Result
67
+ msg = "Expected #{other.inspect} to be a Result"
68
+ raise FunctionalLightService::Monad::NotMonadError, msg
69
+ end
70
+
71
+ success? ? other : self
72
+ end
73
+
74
+ def +(other)
75
+ FunctionalLightService::Deprecations.warn(
76
+ "Result#+ is deprecated and will be removed in a future release; " \
77
+ "combine the two results explicitly"
78
+ )
79
+ unless other.is_a? Result
80
+ msg = "Expected #{other.inspect} to be a Result"
81
+ raise FunctionalLightService::Monad::NotMonadError, msg
82
+ end
83
+
84
+ if success? == other.success?
85
+ self.class.new(value + other.value)
86
+ elsif success?
87
+ other # other is the failure
88
+ else
89
+ self # self is the failure
90
+ end
91
+ end
92
+
93
+ def try(proc = nil, &block)
94
+ map(proc, &block)
95
+ rescue StandardError => e
96
+ Result::Failure.new(e)
97
+ end
98
+
99
+ def >=(proc = nil, &block)
100
+ FunctionalLightService::Deprecations.warn(
101
+ "Result#>= is deprecated; use #try instead"
102
+ )
103
+ try(proc, &block)
104
+ end
105
+ end
106
+ # rubocop:enable Metrics/BlockLength
107
+ end
108
+
109
+ module FunctionalLightService
110
+ module Prelude
111
+ module Result
112
+ # rubocop:disable Naming/MethodName
113
+ def try!(&)
114
+ FunctionalLightService::Result.try!(&)
115
+ end
116
+
117
+ def Success(s)
118
+ FunctionalLightService::Result::Success.new(s)
119
+ end
120
+
121
+ def Failure(f)
122
+ FunctionalLightService::Result::Failure.new(f)
123
+ end
124
+ # rubocop:enable Naming/MethodName
125
+ end
126
+
127
+ include Result
128
+ end
129
+ end
@@ -1,47 +1,48 @@
1
- require 'i18n'
2
-
3
- module FunctionalLightService
4
- class LocalizationAdapter
5
- def failure(message_or_key, action_class, i18n_options = {})
6
- find_translated_message(message_or_key,
7
- action_class,
8
- i18n_options,
9
- :type => :failure)
10
- end
11
-
12
- def success(message_or_key, action_class, i18n_options = {})
13
- find_translated_message(message_or_key,
14
- action_class,
15
- i18n_options,
16
- :type => :success)
17
- end
18
-
19
- private
20
-
21
- def find_translated_message(message_or_key,
22
- action_class,
23
- i18n_options,
24
- type)
25
- if message_or_key.is_a?(Symbol)
26
- i18n_options.merge!(type)
27
- translate(message_or_key, action_class, i18n_options)
28
- else
29
- message_or_key
30
- end
31
- end
32
-
33
- def translate(key, action_class, options = {})
34
- type = options.delete(:type)
35
-
36
- scope = i18n_scope_from_class(action_class, type)
37
- options[:scope] = scope
38
-
39
- I18n.t(key, **options)
40
- end
41
-
42
- def i18n_scope_from_class(action_class, type)
43
- inflector = Dry::Inflector.new
44
- "#{inflector.underscore(action_class.name)}.light_service.#{inflector.pluralize(type)}"
45
- end
46
- end
47
- end
1
+ # frozen_string_literal: true
2
+
3
+ require 'i18n'
4
+
5
+ module FunctionalLightService
6
+ class LocalizationAdapter
7
+ def failure(message_or_key, action_class, i18n_options = {})
8
+ find_translated_message(message_or_key,
9
+ action_class,
10
+ i18n_options,
11
+ :type => :failure)
12
+ end
13
+
14
+ def success(message_or_key, action_class, i18n_options = {})
15
+ find_translated_message(message_or_key,
16
+ action_class,
17
+ i18n_options,
18
+ :type => :success)
19
+ end
20
+
21
+ private
22
+
23
+ def find_translated_message(message_or_key,
24
+ action_class,
25
+ i18n_options,
26
+ type)
27
+ if message_or_key.is_a?(Symbol)
28
+ translate(message_or_key, action_class, i18n_options.merge(type))
29
+ else
30
+ message_or_key
31
+ end
32
+ end
33
+
34
+ def translate(key, action_class, options = {})
35
+ type = options.delete(:type)
36
+
37
+ scope = i18n_scope_from_class(action_class, type)
38
+ options[:scope] = scope
39
+
40
+ I18n.t(key, **options)
41
+ end
42
+
43
+ def i18n_scope_from_class(action_class, type)
44
+ inflector = Dry::Inflector.new
45
+ "#{inflector.underscore(action_class.name)}.light_service.#{inflector.pluralize(type)}"
46
+ end
47
+ end
48
+ end
@@ -1,14 +1,16 @@
1
- module FunctionalLightService
2
- module Organizer
3
- class Execute
4
- def self.run(code_block)
5
- ->(ctx) do
6
- return ctx if ctx.stop_processing?
7
-
8
- code_block.call(ctx)
9
- ctx
10
- end
11
- end
12
- end
13
- end
14
- end
1
+ # frozen_string_literal: true
2
+
3
+ module FunctionalLightService
4
+ module Organizer
5
+ class Execute
6
+ def self.run(code_block)
7
+ ->(ctx) do
8
+ return ctx if ctx.stop_processing?
9
+
10
+ code_block.call(ctx)
11
+ ctx
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -1,25 +1,30 @@
1
- require "dry/inflector"
2
-
3
- module FunctionalLightService
4
- module Organizer
5
- class Iterate
6
- extend ScopedReducable
7
-
8
- def self.run(organizer, collection_key, steps)
9
- ->(ctx) do
10
- return ctx if ctx.stop_processing?
11
-
12
- collection = ctx[collection_key]
13
- inflector = Dry::Inflector.new
14
- item_key = inflector.singularize(collection_key).to_sym
15
- collection.each do |item|
16
- ctx[item_key] = item
17
- ctx = scoped_reduce(organizer, ctx, steps)
18
- end
19
-
20
- ctx
21
- end
22
- end
23
- end
24
- end
25
- end
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/inflector"
4
+
5
+ module FunctionalLightService
6
+ module Organizer
7
+ class Iterate
8
+ extend ScopedReducable
9
+
10
+ INFLECTOR = Dry::Inflector.new
11
+
12
+ def self.run(organizer, collection_key, steps)
13
+ # La singolarizzazione dipende solo dalla chiave: si calcola una volta,
14
+ # non a ogni invocazione dello step (ne' tantomeno per ogni item)
15
+ item_key = INFLECTOR.singularize(collection_key).to_sym
16
+
17
+ ->(ctx) do
18
+ return ctx if ctx.stop_processing?
19
+
20
+ ctx[collection_key].each do |item|
21
+ ctx[item_key] = item
22
+ ctx = scoped_reduce(organizer, ctx, steps)
23
+ end
24
+
25
+ ctx
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -1,17 +1,19 @@
1
- module FunctionalLightService
2
- module Organizer
3
- class ReduceIf
4
- extend ScopedReducable
5
-
6
- def self.run(organizer, condition_block, steps)
7
- ->(ctx) do
8
- return ctx if ctx.stop_processing?
9
-
10
- ctx = scoped_reduce(organizer, ctx, steps) if condition_block.call(ctx)
11
-
12
- ctx
13
- end
14
- end
15
- end
16
- end
17
- end
1
+ # frozen_string_literal: true
2
+
3
+ module FunctionalLightService
4
+ module Organizer
5
+ class ReduceIf
6
+ extend ScopedReducable
7
+
8
+ def self.run(organizer, condition_block, steps)
9
+ ->(ctx) do
10
+ return ctx if ctx.stop_processing?
11
+
12
+ ctx = scoped_reduce(organizer, ctx, steps) if condition_block.call(ctx)
13
+
14
+ ctx
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -1,20 +1,22 @@
1
- module FunctionalLightService
2
- module Organizer
3
- class ReduceUntil
4
- extend ScopedReducable
5
-
6
- def self.run(organizer, condition_block, steps)
7
- ->(ctx) do
8
- return ctx if ctx.stop_processing?
9
-
10
- loop do
11
- ctx = scoped_reduce(organizer, ctx, steps)
12
- break if condition_block.call(ctx) || ctx.failure?
13
- end
14
-
15
- ctx
16
- end
17
- end
18
- end
19
- end
20
- end
1
+ # frozen_string_literal: true
2
+
3
+ module FunctionalLightService
4
+ module Organizer
5
+ class ReduceUntil
6
+ extend ScopedReducable
7
+
8
+ def self.run(organizer, condition_block, steps)
9
+ ->(ctx) do
10
+ return ctx if ctx.stop_processing?
11
+
12
+ loop do
13
+ ctx = scoped_reduce(organizer, ctx, steps)
14
+ break if condition_block.call(ctx) || ctx.failure?
15
+ end
16
+
17
+ ctx
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -1,13 +1,15 @@
1
- module FunctionalLightService
2
- module Organizer
3
- module ScopedReducable
4
- def scoped_reduce(organizer, ctx, steps)
5
- ctx.reset_skip_remaining! unless ctx.failure?
6
- ctx = organizer.with(ctx).reduce([steps])
7
- ctx.reset_skip_remaining! unless ctx.failure?
8
-
9
- ctx
10
- end
11
- end
12
- end
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
- module FunctionalLightService
2
- module Organizer
3
- class WithCallback
4
- extend ScopedReducable
5
-
6
- def self.run(organizer, action, steps)
7
- ->(ctx) do
8
- return ctx if ctx.stop_processing?
9
-
10
- # This will only allow 2 level deep nesting of callbacks
11
- previous_callback = ctx[:callback]
12
-
13
- ctx[:callback] = ->(context) do
14
- ctx = scoped_reduce(organizer, context, steps)
15
- ctx
16
- end
17
-
18
- ctx = action.execute(ctx)
19
- ctx[:callback] = previous_callback
20
-
21
- ctx
22
- end
23
- end
24
- end
25
- end
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