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.
Files changed (77) 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 +45 -0
  6. data/README.md +88 -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/functional/sequencer.rb +144 -0
  23. data/lib/functional-light-service/localization_adapter.rb +48 -47
  24. data/lib/functional-light-service/organizer/execute.rb +16 -14
  25. data/lib/functional-light-service/organizer/iterate.rb +30 -25
  26. data/lib/functional-light-service/organizer/reduce_if.rb +19 -17
  27. data/lib/functional-light-service/organizer/reduce_until.rb +22 -20
  28. data/lib/functional-light-service/organizer/scoped_reducable.rb +15 -13
  29. data/lib/functional-light-service/organizer/with_callback.rb +28 -26
  30. data/lib/functional-light-service/organizer/with_reducer.rb +81 -77
  31. data/lib/functional-light-service/organizer/with_reducer_factory.rb +20 -18
  32. data/lib/functional-light-service/organizer/with_reducer_log_decorator.rb +110 -108
  33. data/lib/functional-light-service/organizer.rb +114 -114
  34. data/lib/functional-light-service/testing/context_factory.rb +48 -42
  35. data/lib/functional-light-service/testing.rb +3 -1
  36. data/lib/functional-light-service/version.rb +5 -3
  37. data/lib/functional-light-service.rb +31 -28
  38. data/spec/acceptance/after_actions_spec.rb +87 -71
  39. data/spec/acceptance/before_actions_spec.rb +115 -98
  40. data/spec/acceptance/custom_log_from_organizer_spec.rb +61 -60
  41. data/spec/acceptance/deprecation_warnings_spec.rb +82 -0
  42. data/spec/acceptance/fail_spec.rb +52 -50
  43. data/spec/acceptance/message_localization_spec.rb +119 -118
  44. data/spec/acceptance/organizer/context_failure_and_skipping_spec.rb +68 -65
  45. data/spec/acceptance/organizer/reduce_if_spec.rb +89 -89
  46. data/spec/acceptance/organizer/with_callback_spec.rb +113 -110
  47. data/spec/acceptance/{not_having_call_method_warning_spec.rb → organizer_entry_point_spec.rb} +10 -7
  48. data/spec/acceptance/rollback_spec.rb +183 -132
  49. data/spec/action_expects_and_promises_spec.rb +97 -93
  50. data/spec/action_promised_keys_spec.rb +126 -122
  51. data/spec/context_spec.rb +289 -197
  52. data/spec/examples/controller_spec.rb +63 -63
  53. data/spec/examples/validate_address_spec.rb +38 -37
  54. data/spec/lib/deterministic/currify_spec.rb +90 -88
  55. data/spec/lib/deterministic/null_spec.rb +6 -1
  56. data/spec/lib/deterministic/option_spec.rb +140 -137
  57. data/spec/lib/deterministic/result/result_map_spec.rb +155 -154
  58. data/spec/lib/deterministic/result/result_shared.rb +3 -2
  59. data/spec/lib/deterministic/result_spec.rb +2 -2
  60. data/spec/lib/deterministic/sequencer_spec.rb +506 -0
  61. data/spec/lib/edge_cases_spec.rb +156 -0
  62. data/spec/lib/enum_spec.rb +1 -1
  63. data/spec/lib/native_pattern_matching_spec.rb +74 -0
  64. data/spec/organizer_spec.rb +115 -114
  65. data/spec/readme_spec.rb +45 -47
  66. data/spec/sample/calculates_order_tax_action_spec.rb +16 -16
  67. data/spec/sample/calculates_tax_spec.rb +1 -1
  68. data/spec/sample/looks_up_tax_percentage_action_spec.rb +55 -55
  69. data/spec/sample/tax/calculates_order_tax_action.rb +10 -9
  70. data/spec/sample/tax/looks_up_tax_percentage_action.rb +28 -27
  71. data/spec/sample/tax/provides_free_shipping_action.rb +11 -10
  72. data/spec/spec_helper.rb +6 -0
  73. data/spec/test_doubles.rb +628 -599
  74. data/spec/testing/context_factory_spec.rb +21 -0
  75. metadata +48 -162
  76. data/lib/functional-light-service/organizer/verify_call_method_exists.rb +0 -29
  77. data/spec/acceptance/include_warning_spec.rb +0 -29
@@ -1,110 +1,113 @@
1
- require 'spec_helper'
2
- require 'test_doubles'
3
-
4
- RSpec.describe FunctionalLightService::Organizer do
5
- describe 'a simple case with a single callback' do
6
- it 'calls the actions defined with callback' do
7
- result = TestDoubles::TestWithCallback.call
8
-
9
- expect(result.counter).to eq(3)
10
- expect(result.total).to eq(6)
11
- end
12
- end
13
-
14
- describe 'a more complex example with nested callbacks' do
15
- class TestWithNestedCallback
16
- extend FunctionalLightService::Organizer
17
-
18
- def self.call(context = {})
19
- with(context).reduce(actions)
20
- end
21
-
22
- def self.actions
23
- [
24
- SetUpNestedContextAction,
25
- with_callback(IterateOuterCollectionAction,
26
- [IncrementOuterCountAction,
27
- with_callback(TestDoubles::IterateCollectionAction,
28
- [TestDoubles::IncrementCountAction,
29
- TestDoubles::AddToTotalAction])])
30
- ]
31
- end
32
- end
33
-
34
- class SetUpNestedContextAction
35
- extend FunctionalLightService::Action
36
- promises :outer_numbers, :outer_counter,
37
- :numbers, :counter, :total
38
-
39
- executed do |ctx|
40
- ctx.outer_numbers = [12, 17]
41
- ctx.outer_counter = 0
42
- ctx.numbers = [1, 2, 3]
43
- ctx.counter = 0
44
- ctx.total = 0
45
- end
46
- end
47
-
48
- class IterateOuterCollectionAction
49
- extend FunctionalLightService::Action
50
- expects :outer_numbers, :callback
51
- promises :outer_number
52
-
53
- executed do |ctx|
54
- ctx.outer_numbers.each do |outer_number|
55
- ctx.outer_number = outer_number
56
- ctx.callback.call(ctx)
57
- end
58
- end
59
- end
60
-
61
- class IncrementOuterCountAction
62
- extend FunctionalLightService::Action
63
- expects :outer_counter
64
-
65
- executed do |ctx|
66
- ctx.outer_counter = ctx.outer_counter + 1
67
- end
68
- end
69
-
70
- it 'calls both the action and the nested callbacks' do
71
- result = TestWithNestedCallback.call
72
-
73
- expect(result.outer_counter).to eq(2)
74
- # Counts and total are the duplicates of
75
- # what you'll see in the simple spec,
76
- # as the internal callback logic is called
77
- # twice due to 2 items in the outer_numbers
78
- # collection.
79
- expect(result.counter).to eq(6)
80
- expect(result.total).to eq(12)
81
- end
82
- end
83
-
84
- describe 'with failed or skipped context' do
85
- class TestWithFailureCallback
86
- extend FunctionalLightService::Organizer
87
-
88
- def self.call(context = {})
89
- with(context).reduce(actions)
90
- end
91
-
92
- def self.actions
93
- [
94
- TestDoubles::SetUpContextAction,
95
- with_callback(TestDoubles::IterateCollectionAction,
96
- [TestDoubles::IncrementCountAction,
97
- TestDoubles::FailureAction])
98
- ]
99
- end
100
- end
101
-
102
- it 'will not process the routine' do
103
- result = TestWithFailureCallback.call
104
-
105
- expect(result).to be_failure
106
- expect(result.counter).to eq(1)
107
- expect(result.total).to eq(0)
108
- end
109
- end
110
- end
1
+ require 'spec_helper'
2
+ require 'test_doubles'
3
+
4
+ RSpec.describe FunctionalLightService::Organizer do
5
+ describe 'a simple case with a single callback' do
6
+ it 'calls the actions defined with callback' do
7
+ result = TestDoubles::TestWithCallback.call
8
+
9
+ expect(result.counter).to eq(3)
10
+ expect(result.total).to eq(6)
11
+ end
12
+ end
13
+
14
+ describe 'a more complex example with nested callbacks' do
15
+ class TestWithNestedCallback
16
+ extend FunctionalLightService::Organizer
17
+
18
+ def self.call(context = {})
19
+ with(context).reduce(actions)
20
+ end
21
+
22
+ def self.actions
23
+ [
24
+ SetUpNestedContextAction,
25
+ with_callback(IterateOuterCollectionAction,
26
+ [IncrementOuterCountAction,
27
+ with_callback(TestDoubles::IterateCollectionAction,
28
+ [TestDoubles::IncrementCountAction,
29
+ TestDoubles::AddToTotalAction])])
30
+ ]
31
+ end
32
+ end
33
+
34
+ class SetUpNestedContextAction
35
+ extend FunctionalLightService::Action
36
+
37
+ promises :outer_numbers, :outer_counter,
38
+ :numbers, :counter, :total
39
+
40
+ executed do |ctx|
41
+ ctx.outer_numbers = [12, 17]
42
+ ctx.outer_counter = 0
43
+ ctx.numbers = [1, 2, 3]
44
+ ctx.counter = 0
45
+ ctx.total = 0
46
+ end
47
+ end
48
+
49
+ class IterateOuterCollectionAction
50
+ extend FunctionalLightService::Action
51
+
52
+ expects :outer_numbers, :callback
53
+ promises :outer_number
54
+
55
+ executed do |ctx|
56
+ ctx.outer_numbers.each do |outer_number|
57
+ ctx.outer_number = outer_number
58
+ ctx.callback.call(ctx)
59
+ end
60
+ end
61
+ end
62
+
63
+ class IncrementOuterCountAction
64
+ extend FunctionalLightService::Action
65
+
66
+ expects :outer_counter
67
+
68
+ executed do |ctx|
69
+ ctx.outer_counter = ctx.outer_counter + 1
70
+ end
71
+ end
72
+
73
+ it 'calls both the action and the nested callbacks' do
74
+ result = TestWithNestedCallback.call
75
+
76
+ expect(result.outer_counter).to eq(2)
77
+ # Counts and total are the duplicates of
78
+ # what you'll see in the simple spec,
79
+ # as the internal callback logic is called
80
+ # twice due to 2 items in the outer_numbers
81
+ # collection.
82
+ expect(result.counter).to eq(6)
83
+ expect(result.total).to eq(12)
84
+ end
85
+ end
86
+
87
+ describe 'with failed or skipped context' do
88
+ class TestWithFailureCallback
89
+ extend FunctionalLightService::Organizer
90
+
91
+ def self.call(context = {})
92
+ with(context).reduce(actions)
93
+ end
94
+
95
+ def self.actions
96
+ [
97
+ TestDoubles::SetUpContextAction,
98
+ with_callback(TestDoubles::IterateCollectionAction,
99
+ [TestDoubles::IncrementCountAction,
100
+ TestDoubles::FailureAction])
101
+ ]
102
+ end
103
+ end
104
+
105
+ it 'will not process the routine' do
106
+ result = TestWithFailureCallback.call
107
+
108
+ expect(result).to be_failure
109
+ expect(result.counter).to eq(1)
110
+ expect(result.total).to eq(0)
111
+ end
112
+ end
113
+ end
@@ -1,9 +1,9 @@
1
1
  require 'spec_helper'
2
2
  require 'test_doubles'
3
3
 
4
- describe "Organizer should invoke with/reduce from a call method" do
5
- context "when the organizer does not have a `call` method" do
6
- it "gives warning" do
4
+ describe "Organizer entry point" do
5
+ context "when the organizer entry method is not named `call`" do
6
+ it "works without emitting any warning" do
7
7
  class OrganizerWithoutCallMethod
8
8
  extend FunctionalLightService::Organizer
9
9
 
@@ -11,14 +11,16 @@ describe "Organizer should invoke with/reduce from a call method" do
11
11
  reduce([])
12
12
  end
13
13
  end
14
- expect do
15
- OrganizerWithoutCallMethod.do_something
16
- end.to output(/The <OrganizerWithoutCallMethod> class is an organizer/).to_stdout
14
+
15
+ result = nil
16
+ expect { result = OrganizerWithoutCallMethod.do_something }
17
+ .not_to output.to_stdout
18
+ expect(result).to be_a_kind_of(FunctionalLightService::Context)
17
19
  end
18
20
  end
19
21
 
20
22
  context "when the organizer has the `call` method" do
21
- it "does not issue a warning" do
23
+ it "works without emitting any warning" do
22
24
  class OrganizerWithCallMethod
23
25
  extend FunctionalLightService::Organizer
24
26
 
@@ -26,6 +28,7 @@ describe "Organizer should invoke with/reduce from a call method" do
26
28
  reduce([])
27
29
  end
28
30
  end
31
+
29
32
  expect(OrganizerWithCallMethod.call).to be_a_kind_of(FunctionalLightService::Context)
30
33
  end
31
34
  end
@@ -1,132 +1,183 @@
1
- require 'spec_helper'
2
- require 'test_doubles'
3
-
4
- class RollbackOrganizer
5
- extend FunctionalLightService::Organizer
6
-
7
- def self.call(number)
8
- with(:number => number).reduce(
9
- AddsOneWithRollbackAction,
10
- TestDoubles::AddsTwoAction,
11
- AddsThreeWithRollbackAction
12
- )
13
- end
14
- end
15
-
16
- class AddsOneWithRollbackAction
17
- extend FunctionalLightService::Action
18
- expects :number
19
- promises :number
20
-
21
- executed do |context|
22
- context.fail_with_rollback! if context.number.zero?
23
-
24
- context.number += 1
25
- end
26
-
27
- rolled_back do |context|
28
- context.number -= 1
29
- end
30
- end
31
-
32
- class AddsThreeWithRollbackAction
33
- extend FunctionalLightService::Action
34
- expects :number
35
-
36
- executed do |context|
37
- context.number = context.number + 3
38
-
39
- context.fail_with_rollback!("I did not like this!")
40
- end
41
-
42
- rolled_back do |context|
43
- context.number -= 3
44
- end
45
- end
46
-
47
- class RollbackOrganizerWithNoRollback
48
- extend FunctionalLightService::Organizer
49
-
50
- def self.call(number)
51
- with(:number => number).reduce(
52
- TestDoubles::AddsOneAction,
53
- TestDoubles::AddsTwoAction,
54
- AddsThreeWithNoRollbackAction
55
- )
56
- end
57
- end
58
-
59
- class AddsThreeWithNoRollbackAction
60
- extend FunctionalLightService::Action
61
- expects :number
62
-
63
- executed do |context|
64
- context.number = context.number + 3
65
-
66
- context.fail_with_rollback!("I did not like this!")
67
- end
68
- end
69
-
70
- class RollbackOrganizerWithMiddleRollback
71
- extend FunctionalLightService::Organizer
72
-
73
- def self.call(number)
74
- with(:number => number).reduce(
75
- TestDoubles::AddsOneAction,
76
- AddsTwoActionWithRollback,
77
- TestDoubles::AddsThreeAction
78
- )
79
- end
80
- end
81
-
82
- class AddsTwoActionWithRollback
83
- extend FunctionalLightService::Action
84
- expects :number
85
-
86
- executed do |context|
87
- context.number = context.number + 2
88
-
89
- context.fail_with_rollback!("I did not like this a bit!")
90
- end
91
-
92
- rolled_back do |context|
93
- context.number -= 2
94
- end
95
- end
96
-
97
- describe "Rolling back actions when there is a failure" do
98
- it "Adds 1, 2, 3 to 1 and rolls back " do
99
- result = RollbackOrganizer.call 1
100
- number = result.fetch(:number)
101
-
102
- expect(result).to be_failure
103
- expect(result.message).to eq("I did not like this!")
104
- expect(number).to eq(3)
105
- end
106
-
107
- it "won't error out when actions don't define rollback" do
108
- result = RollbackOrganizerWithNoRollback.call 1
109
- number = result.fetch(:number)
110
-
111
- expect(result).to be_failure
112
- expect(result.message).to eq("I did not like this!")
113
- expect(number).to eq(7)
114
- end
115
-
116
- it "rolls back properly when triggered with an action in the middle" do
117
- result = RollbackOrganizerWithMiddleRollback.call 1
118
- number = result.fetch(:number)
119
-
120
- expect(result).to be_failure
121
- expect(result.message).to eq("I did not like this a bit!")
122
- expect(number).to eq(2)
123
- end
124
-
125
- it "rolls back from the first action" do
126
- result = RollbackOrganizer.call 0
127
- number = result.fetch(:number)
128
-
129
- expect(result).to be_failure
130
- expect(number).to eq(-1)
131
- end
132
- end
1
+ require 'spec_helper'
2
+ require 'test_doubles'
3
+
4
+ class RollbackOrganizer
5
+ extend FunctionalLightService::Organizer
6
+
7
+ def self.call(number)
8
+ with(:number => number).reduce(
9
+ AddsOneWithRollbackAction,
10
+ TestDoubles::AddsTwoAction,
11
+ AddsThreeWithRollbackAction
12
+ )
13
+ end
14
+ end
15
+
16
+ class AddsOneWithRollbackAction
17
+ extend FunctionalLightService::Action
18
+
19
+ expects :number
20
+ promises :number
21
+
22
+ executed do |context|
23
+ context.fail_with_rollback! if context.number.zero?
24
+
25
+ context.number += 1
26
+ end
27
+
28
+ rolled_back do |context|
29
+ context.number -= 1
30
+ end
31
+ end
32
+
33
+ class AddsThreeWithRollbackAction
34
+ extend FunctionalLightService::Action
35
+
36
+ expects :number
37
+
38
+ executed do |context|
39
+ context.number = context.number + 3
40
+
41
+ context.fail_with_rollback!("I did not like this!")
42
+ end
43
+
44
+ rolled_back do |context|
45
+ context.number -= 3
46
+ end
47
+ end
48
+
49
+ class RollbackOrganizerWithNoRollback
50
+ extend FunctionalLightService::Organizer
51
+
52
+ def self.call(number)
53
+ with(:number => number).reduce(
54
+ TestDoubles::AddsOneAction,
55
+ TestDoubles::AddsTwoAction,
56
+ AddsThreeWithNoRollbackAction
57
+ )
58
+ end
59
+ end
60
+
61
+ class AddsThreeWithNoRollbackAction
62
+ extend FunctionalLightService::Action
63
+
64
+ expects :number
65
+
66
+ executed do |context|
67
+ context.number = context.number + 3
68
+
69
+ context.fail_with_rollback!("I did not like this!")
70
+ end
71
+ end
72
+
73
+ class RollbackOrganizerWithMiddleRollback
74
+ extend FunctionalLightService::Organizer
75
+
76
+ def self.call(number)
77
+ with(:number => number).reduce(
78
+ TestDoubles::AddsOneAction,
79
+ AddsTwoActionWithRollback,
80
+ TestDoubles::AddsThreeAction
81
+ )
82
+ end
83
+ end
84
+
85
+ class AddsTwoActionWithRollback
86
+ extend FunctionalLightService::Action
87
+
88
+ expects :number
89
+
90
+ executed do |context|
91
+ context.number = context.number + 2
92
+
93
+ context.fail_with_rollback!("I did not like this a bit!")
94
+ end
95
+
96
+ rolled_back do |context|
97
+ context.number -= 2
98
+ end
99
+ end
100
+
101
+ class RollbackOrganizerWithDuplicatedAction
102
+ extend FunctionalLightService::Organizer
103
+
104
+ def self.call(ctx)
105
+ with(ctx).reduce(
106
+ TracksRollbackAction,
107
+ FailsOnSecondRunWithRollbackAction,
108
+ TracksRollbackAction,
109
+ FailsOnSecondRunWithRollbackAction
110
+ )
111
+ end
112
+ end
113
+
114
+ class TracksRollbackAction
115
+ extend FunctionalLightService::Action
116
+
117
+ executed do |context|
118
+ (context[:executed] ||= []) << :tracks
119
+ end
120
+
121
+ rolled_back do |context|
122
+ (context[:rolled_back] ||= []) << :tracks
123
+ end
124
+ end
125
+
126
+ class FailsOnSecondRunWithRollbackAction
127
+ extend FunctionalLightService::Action
128
+
129
+ executed do |context|
130
+ (context[:executed] ||= []) << :fails
131
+ context.fail_with_rollback!("boom") if context[:executed].count(:fails) == 2
132
+ end
133
+
134
+ rolled_back do |context|
135
+ (context[:rolled_back] ||= []) << :fails
136
+ end
137
+ end
138
+
139
+ describe "Rolling back actions when there is a failure" do
140
+ it "Adds 1, 2, 3 to 1 and rolls back " do
141
+ result = RollbackOrganizer.call 1
142
+ number = result.fetch(:number)
143
+
144
+ expect(result).to be_failure
145
+ expect(result.message).to eq("I did not like this!")
146
+ expect(number).to eq(3)
147
+ end
148
+
149
+ it "won't error out when actions don't define rollback" do
150
+ result = RollbackOrganizerWithNoRollback.call 1
151
+ number = result.fetch(:number)
152
+
153
+ expect(result).to be_failure
154
+ expect(result.message).to eq("I did not like this!")
155
+ expect(number).to eq(7)
156
+ end
157
+
158
+ it "rolls back properly when triggered with an action in the middle" do
159
+ result = RollbackOrganizerWithMiddleRollback.call 1
160
+ number = result.fetch(:number)
161
+
162
+ expect(result).to be_failure
163
+ expect(result.message).to eq("I did not like this a bit!")
164
+ expect(number).to eq(2)
165
+ end
166
+
167
+ it "rolls back from the first action" do
168
+ result = RollbackOrganizer.call 0
169
+ number = result.fetch(:number)
170
+
171
+ expect(result).to be_failure
172
+ expect(number).to eq(-1)
173
+ end
174
+
175
+ it "rolls back every executed action when the same action appears twice" do
176
+ result = RollbackOrganizerWithDuplicatedAction.call({})
177
+
178
+ expect(result).to be_failure
179
+ expect(result[:executed]).to eq(%i[tracks fails tracks fails])
180
+ # tutte e 4 le azioni eseguite vengono compensate, in ordine inverso
181
+ expect(result[:rolled_back]).to eq(%i[fails tracks fails tracks])
182
+ end
183
+ end