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,98 +1,115 @@
1
- require 'spec_helper'
2
- require 'test_doubles'
3
-
4
- RSpec.describe 'Action before_actions' do
5
- describe 'works with simple organizers - from outside' do
6
- it 'can be used to inject code block before each action' do
7
- TestDoubles::AdditionOrganizer.before_actions = ->(ctx) do
8
- ctx.number -= 2 if ctx.current_action == TestDoubles::AddsThreeAction
9
- end
10
-
11
- result = TestDoubles::AdditionOrganizer.call(0)
12
-
13
- expect(result.fetch(:number)).to eq(4)
14
- end
15
-
16
- it 'works with iterator' do
17
- TestDoubles::TestIterate.before_actions = [
18
- ->(ctx) { ctx.number -= 2 if ctx.current_action == TestDoubles::AddsOneAction }
19
- ]
20
-
21
- result = TestDoubles::TestIterate.call(:number => 0,
22
- :counters => [1, 2, 3, 4])
23
-
24
- expect(result).to be_success
25
- expect(result.number).to eq(-4)
26
- end
27
- end
28
-
29
- describe 'can be added to organizers declaratively' do
30
- module BeforeActions
31
- class AdditionOrganizer
32
- extend FunctionalLightService::Organizer
33
- before_actions (->(ctx) do
34
- ctx.number -= 2 if ctx.current_action == TestDoubles::AddsOneAction
35
- end),
36
- (->(ctx) do
37
- ctx.number -= 3 if ctx.current_action == TestDoubles::AddsThreeAction
38
- end)
39
-
40
- def self.call(number)
41
- with(:number => number).reduce(actions)
42
- end
43
-
44
- def self.actions
45
- [
46
- TestDoubles::AddsOneAction,
47
- TestDoubles::AddsTwoAction,
48
- TestDoubles::AddsThreeAction
49
- ]
50
- end
51
- end
52
- end
53
-
54
- it 'accepts before_actions hook lambdas from organizer' do
55
- result = BeforeActions::AdditionOrganizer.call(0)
56
-
57
- expect(result.fetch(:number)).to eq(1)
58
- end
59
- end
60
-
61
- describe 'works with callbacks' do
62
- it 'can interact with actions from the outside' do
63
- TestDoubles::TestWithCallback.before_actions = [
64
- ->(ctx) { ctx.total -= 1000 if ctx.current_action == TestDoubles::AddToTotalAction }
65
- ]
66
- result = TestDoubles::TestWithCallback.call
67
-
68
- expect(result.counter).to eq(3)
69
- expect(result.total).to eq(-2994)
70
- end
71
- end
72
-
73
- describe 'can halt all execution with a raised error' do
74
- it 'does not call the rest of the callback steps' do
75
- class SkipContextError < StandardError
76
- attr_reader :ctx
77
-
78
- def initialize(msg, ctx)
79
- @ctx = ctx
80
- super(msg)
81
- end
82
- end
83
- TestDoubles::TestWithCallback.before_actions = [
84
- ->(ctx) do
85
- if ctx.current_action == TestDoubles::IncrementCountAction
86
- ctx.total -= 1000
87
- raise SkipContextError.new("stop context now", ctx)
88
- end
89
- end
90
- ]
91
- begin
92
- TestDoubles::TestWithCallback.call
93
- rescue SkipContextError => e
94
- expect(e.ctx).not_to be_empty
95
- end
96
- end
97
- end
98
- end
1
+ require 'spec_helper'
2
+ require 'test_doubles'
3
+
4
+ RSpec.describe 'Action before_actions' do
5
+ # Gli hook restano sulla classe (non vengono piu consumati da #with):
6
+ # i test double sono condivisi, quindi vanno ripuliti dopo ogni esempio
7
+ after do
8
+ TestDoubles::AdditionOrganizer.before_actions = nil
9
+ TestDoubles::TestIterate.before_actions = nil
10
+ TestDoubles::TestWithCallback.before_actions = nil
11
+ end
12
+
13
+ describe 'works with simple organizers - from outside' do
14
+ it 'can be used to inject code block before each action' do
15
+ TestDoubles::AdditionOrganizer.before_actions = ->(ctx) do
16
+ ctx.number -= 2 if ctx.current_action == TestDoubles::AddsThreeAction
17
+ end
18
+
19
+ result = TestDoubles::AdditionOrganizer.call(0)
20
+
21
+ expect(result.fetch(:number)).to eq(4)
22
+ end
23
+
24
+ it 'works with iterator' do
25
+ TestDoubles::TestIterate.before_actions = [
26
+ ->(ctx) { ctx.number -= 2 if ctx.current_action == TestDoubles::AddsOneAction }
27
+ ]
28
+
29
+ result = TestDoubles::TestIterate.call(:number => 0,
30
+ :counters => [1, 2, 3, 4])
31
+
32
+ expect(result).to be_success
33
+ expect(result.number).to eq(-4)
34
+ end
35
+ end
36
+
37
+ describe 'can be added to organizers declaratively' do
38
+ module BeforeActions
39
+ class AdditionOrganizer
40
+ extend FunctionalLightService::Organizer
41
+
42
+ before_actions ->(ctx) do
43
+ ctx.number -= 2 if ctx.current_action == TestDoubles::AddsOneAction
44
+ end,
45
+ ->(ctx) do
46
+ ctx.number -= 3 if ctx.current_action == TestDoubles::AddsThreeAction
47
+ end
48
+
49
+ def self.call(number)
50
+ with(:number => number).reduce(actions)
51
+ end
52
+
53
+ def self.actions
54
+ [
55
+ TestDoubles::AddsOneAction,
56
+ TestDoubles::AddsTwoAction,
57
+ TestDoubles::AddsThreeAction
58
+ ]
59
+ end
60
+ end
61
+ end
62
+
63
+ it 'accepts before_actions hook lambdas from organizer' do
64
+ result = BeforeActions::AdditionOrganizer.call(0)
65
+
66
+ expect(result.fetch(:number)).to eq(1)
67
+ end
68
+
69
+ it 'keeps the declarative hooks on every subsequent call' do
70
+ first = BeforeActions::AdditionOrganizer.call(0)
71
+ second = BeforeActions::AdditionOrganizer.call(0)
72
+
73
+ expect(first.fetch(:number)).to eq(1)
74
+ expect(second.fetch(:number)).to eq(1)
75
+ end
76
+ end
77
+
78
+ describe 'works with callbacks' do
79
+ it 'can interact with actions from the outside' do
80
+ TestDoubles::TestWithCallback.before_actions = [
81
+ ->(ctx) { ctx.total -= 1000 if ctx.current_action == TestDoubles::AddToTotalAction }
82
+ ]
83
+ result = TestDoubles::TestWithCallback.call
84
+
85
+ expect(result.counter).to eq(3)
86
+ expect(result.total).to eq(-2994)
87
+ end
88
+ end
89
+
90
+ describe 'can halt all execution with a raised error' do
91
+ it 'does not call the rest of the callback steps' do
92
+ class SkipContextError < StandardError
93
+ attr_reader :ctx
94
+
95
+ def initialize(msg, ctx)
96
+ @ctx = ctx
97
+ super(msg)
98
+ end
99
+ end
100
+ TestDoubles::TestWithCallback.before_actions = [
101
+ ->(ctx) do
102
+ if ctx.current_action == TestDoubles::IncrementCountAction
103
+ ctx.total -= 1000
104
+ raise SkipContextError.new("stop context now", ctx)
105
+ end
106
+ end
107
+ ]
108
+ begin
109
+ TestDoubles::TestWithCallback.call
110
+ rescue SkipContextError => e
111
+ expect(e.ctx).not_to be_empty
112
+ end
113
+ end
114
+ end
115
+ end
@@ -1,60 +1,61 @@
1
- require 'spec_helper'
2
-
3
- describe "Log from an organizer with a custom logger" do
4
- context "when overriding the global FunctionalLightService organizer" do
5
- let(:global_logger_organizer) do
6
- Class.new do
7
- extend FunctionalLightService::Organizer
8
-
9
- def self.call(number)
10
- with(:number => number).reduce(actions)
11
- end
12
-
13
- def self.actions
14
- [
15
- TestDoubles::AddsOneAction,
16
- TestDoubles::AddsTwoAction,
17
- TestDoubles::AddsThreeAction
18
- ]
19
- end
20
- end
21
- end
22
-
23
- let(:global_logger_string) { StringIO.new }
24
-
25
- let(:custom_logger_string) { StringIO.new }
26
- let(:custom_logger_organizer) do
27
- custom_logger = Logger.new(custom_logger_string)
28
-
29
- Class.new do
30
- extend FunctionalLightService::Organizer
31
- log_with custom_logger
32
-
33
- def self.call(coffee, this_hot = :very_hot)
34
- with(:milk => this_hot, :coffee => coffee)
35
- .reduce(TestDoubles::MakesLatteAction,
36
- TestDoubles::AddsTwoActionWithFetch)
37
- end
38
- end
39
- end
40
-
41
- before do
42
- @original_global_logger = FunctionalLightService::Configuration.logger
43
- FunctionalLightService::Configuration.logger = Logger.new(global_logger_string)
44
- end
45
-
46
- it "logs in own logger" do
47
- global_logger_organizer.call(1)
48
- custom_logger_organizer.call(:coffee => "Cappucino")
49
-
50
- expect(custom_logger_string.string).to include("MakesLatteAction")
51
- expect(custom_logger_string.string).to_not include("AddsOneAction")
52
- expect(global_logger_string.string).to include("AddsOneAction")
53
- expect(global_logger_string.string).to_not include("MakesLatteAction")
54
- end
55
-
56
- after do
57
- FunctionalLightService::Configuration.logger = @original_global_logger
58
- end
59
- end
60
- end
1
+ require 'spec_helper'
2
+
3
+ describe "Log from an organizer with a custom logger" do
4
+ context "when overriding the global FunctionalLightService organizer" do
5
+ let(:global_logger_organizer) do
6
+ Class.new do
7
+ extend FunctionalLightService::Organizer
8
+
9
+ def self.call(number)
10
+ with(:number => number).reduce(actions)
11
+ end
12
+
13
+ def self.actions
14
+ [
15
+ TestDoubles::AddsOneAction,
16
+ TestDoubles::AddsTwoAction,
17
+ TestDoubles::AddsThreeAction
18
+ ]
19
+ end
20
+ end
21
+ end
22
+
23
+ let(:global_logger_string) { StringIO.new }
24
+
25
+ let(:custom_logger_string) { StringIO.new }
26
+ let(:custom_logger_organizer) do
27
+ custom_logger = Logger.new(custom_logger_string)
28
+
29
+ Class.new do
30
+ extend FunctionalLightService::Organizer
31
+
32
+ log_with custom_logger
33
+
34
+ def self.call(coffee, this_hot = :very_hot)
35
+ with(:milk => this_hot, :coffee => coffee)
36
+ .reduce(TestDoubles::MakesLatteAction,
37
+ TestDoubles::AddsTwoActionWithFetch)
38
+ end
39
+ end
40
+ end
41
+
42
+ before do
43
+ @original_global_logger = FunctionalLightService::Configuration.logger
44
+ FunctionalLightService::Configuration.logger = Logger.new(global_logger_string)
45
+ end
46
+
47
+ it "logs in own logger" do
48
+ global_logger_organizer.call(1)
49
+ custom_logger_organizer.call(:coffee => "Cappucino")
50
+
51
+ expect(custom_logger_string.string).to include("MakesLatteAction")
52
+ expect(custom_logger_string.string).to_not include("AddsOneAction")
53
+ expect(global_logger_string.string).to include("AddsOneAction")
54
+ expect(global_logger_string.string).to_not include("MakesLatteAction")
55
+ end
56
+
57
+ after do
58
+ FunctionalLightService::Configuration.logger = @original_global_logger
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,82 @@
1
+ require 'spec_helper'
2
+ # Maybe()/Null e il monkey-patch di Object sono opt-in: non fanno parte
3
+ # del require chain di default della gem
4
+ require 'functional-light-service/functional/maybe'
5
+
6
+ describe "deprecation warnings" do
7
+ around do |example|
8
+ FunctionalLightService::Deprecations.silenced = false
9
+ FunctionalLightService::Deprecations.reset!
10
+ example.run
11
+ ensure
12
+ FunctionalLightService::Deprecations.silenced = true
13
+ FunctionalLightService::Deprecations.reset!
14
+ end
15
+
16
+ context "when including FunctionalLightService::Organizer" do
17
+ it "warns on stderr" do
18
+ expect do
19
+ class OrganizerIncludingLS
20
+ include FunctionalLightService::Organizer
21
+ end
22
+ end.to output(/Including FunctionalLightService::Organizer is deprecated/).to_stderr
23
+ end
24
+ end
25
+
26
+ context "when including FunctionalLightService::Action" do
27
+ it "warns on stderr" do
28
+ expect do
29
+ class ActionIncludingLS
30
+ include FunctionalLightService::Action
31
+ end
32
+ end.to output(/Including FunctionalLightService::Action is deprecated/).to_stderr
33
+ end
34
+ end
35
+
36
+ context "when using Maybe/Null" do
37
+ it "warns once per process" do
38
+ expect { Maybe(nil) }
39
+ .to output(%r{Maybe\(\)/Null are deprecated}).to_stderr
40
+ # warn-once: la seconda invocazione non emette nulla
41
+ expect { Maybe(nil) }.not_to output.to_stderr
42
+ end
43
+ end
44
+
45
+ context "when using the exotic operators" do
46
+ include FunctionalLightService::Prelude::Result
47
+
48
+ it "Result#>= warns and delegates to #try" do
49
+ result = nil
50
+ expect { result = Success(1) >= ->(v) { Success(v + 1) } }
51
+ .to output(/Result#>= is deprecated/).to_stderr
52
+ expect(result).to eq(Success(2))
53
+ end
54
+
55
+ it "Result#<< warns and delegates to #pipe" do
56
+ expect { Success(1) << ->(_v) {} }
57
+ .to output(/Result#<< is deprecated/).to_stderr
58
+ end
59
+
60
+ it "Result#+ warns and still combines" do
61
+ result = nil
62
+ expect { result = Success(1) + Success(2) }
63
+ .to output(/Result#\+ is deprecated/).to_stderr
64
+ expect(result).to eq(Success(3))
65
+ end
66
+
67
+ it "Option#+ warns and still combines" do
68
+ some = FunctionalLightService::Option::Some
69
+ result = nil
70
+ expect { result = some.new(1) + some.new(2) }
71
+ .to output(/Option#\+ is deprecated/).to_stderr
72
+ expect(result).to eq(some.new(3))
73
+ end
74
+ end
75
+
76
+ context "when silenced" do
77
+ it "emits nothing" do
78
+ FunctionalLightService::Deprecations.silenced = true
79
+ expect { Maybe(nil) }.not_to output.to_stderr
80
+ end
81
+ end
82
+ end
@@ -1,50 +1,52 @@
1
- require 'spec_helper'
2
-
3
- RSpec.describe "fail_and_return!" do
4
- describe "returns immediately from executed block" do
5
- class FailAndReturnAction
6
- extend FunctionalLightService::Action
7
- promises :one, :two
8
-
9
- executed do |ctx|
10
- ctx.one = 1
11
- # Have to set it in Context
12
- ctx.two = nil
13
-
14
- ctx.fail_and_return!('Something went wrong')
15
- ctx.two = 2
16
- end
17
- end
18
-
19
- it "returns immediately from executed block" do
20
- result = FailAndReturnAction.execute
21
-
22
- expect(result).to be_failure
23
- expect(result.two).to be_nil
24
- end
25
- end
26
-
27
- describe "accepts error_code option" do
28
- class FailAndReturnWithErrorCodeAction
29
- extend FunctionalLightService::Action
30
- promises :one, :two
31
-
32
- executed do |ctx|
33
- ctx.one = 1
34
- # Have to set it in Context
35
- ctx.two = nil
36
-
37
- ctx.fail_and_return!('Something went wrong', :error_code => 401)
38
- ctx.two = 2
39
- end
40
- end
41
-
42
- it "returned context contains the error_code" do
43
- result = FailAndReturnWithErrorCodeAction.execute
44
-
45
- expect(result).to be_failure
46
- expect(result.error_code).to eq 401
47
- expect(result.two).to be_nil
48
- end
49
- end
50
- end
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe "fail_and_return!" do
4
+ describe "returns immediately from executed block" do
5
+ class FailAndReturnAction
6
+ extend FunctionalLightService::Action
7
+
8
+ promises :one, :two
9
+
10
+ executed do |ctx|
11
+ ctx.one = 1
12
+ # Have to set it in Context
13
+ ctx.two = nil
14
+
15
+ ctx.fail_and_return!('Something went wrong')
16
+ ctx.two = 2
17
+ end
18
+ end
19
+
20
+ it "returns immediately from executed block" do
21
+ result = FailAndReturnAction.execute
22
+
23
+ expect(result).to be_failure
24
+ expect(result.two).to be_nil
25
+ end
26
+ end
27
+
28
+ describe "accepts error_code option" do
29
+ class FailAndReturnWithErrorCodeAction
30
+ extend FunctionalLightService::Action
31
+
32
+ promises :one, :two
33
+
34
+ executed do |ctx|
35
+ ctx.one = 1
36
+ # Have to set it in Context
37
+ ctx.two = nil
38
+
39
+ ctx.fail_and_return!('Something went wrong', :error_code => 401)
40
+ ctx.two = 2
41
+ end
42
+ end
43
+
44
+ it "returned context contains the error_code" do
45
+ result = FailAndReturnWithErrorCodeAction.execute
46
+
47
+ expect(result).to be_failure
48
+ expect(result.error_code).to eq 401
49
+ expect(result.two).to be_nil
50
+ end
51
+ end
52
+ end