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,118 +1,119 @@
1
- require "spec_helper"
2
- require "test_doubles"
3
-
4
- class TestsLocalizationAdapter
5
- extend FunctionalLightService::Organizer
6
-
7
- def self.call(pass_or_fail, message_or_key, i18n_options = {})
8
- with(
9
- :pass_or_fail => pass_or_fail,
10
- :message_or_key => message_or_key,
11
- :i18n_options => i18n_options
12
- ).reduce(TestsLocalizationInvocationOptionsAction)
13
- end
14
- end
15
-
16
- class TestsLocalizationInvocationOptionsAction
17
- extend FunctionalLightService::Action
18
- expects :pass_or_fail, :message_or_key, :i18n_options
19
-
20
- executed do |context|
21
- if context.pass_or_fail == true
22
- context.succeed!(context.message_or_key, context.i18n_options)
23
- else
24
- context.fail!(context.message_or_key, context.i18n_options)
25
- end
26
- end
27
- end
28
-
29
- def pass_with(message_or_key, i18n_options = {})
30
- TestsLocalizationAdapter.call(true, message_or_key, i18n_options)
31
- end
32
-
33
- def fail_with(message_or_key, i18n_options = {})
34
- TestsLocalizationAdapter.call(false, message_or_key, i18n_options)
35
- end
36
-
37
- describe "Localization Adapter" do
38
- before do
39
- I18n.backend.store_translations(
40
- :en,
41
- :tests_localization_invocation_options_action =>
42
- {
43
- :light_service => {
44
- :failures => {
45
- :some_failure_reason => "This has failed",
46
- :failure_with_interpolation => "Failed with %{reason}"
47
- },
48
- :successes => {
49
- :some_success_reason => "This has passed",
50
- :success_with_interpolation => "Passed with %{reason}"
51
- }
52
- }
53
- }
54
- )
55
- end
56
-
57
- describe "passing a simple string message" do
58
- describe "by failing the context" do
59
- it "returns the string" do
60
- result = fail_with("string message")
61
-
62
- expect(result).to be_failure
63
- expect(result.message).to eq("string message")
64
- end
65
- end
66
-
67
- describe "by passing the context" do
68
- it "returns the string" do
69
- result = pass_with("string message")
70
-
71
- expect(result).to be_success
72
- expect(result.message).to eq("string message")
73
- end
74
- end
75
- end
76
-
77
- describe "passing a Symbol" do
78
- describe "by failing the context" do
79
- it "performs a translation" do
80
- result = fail_with(:some_failure_reason)
81
-
82
- expect(result).to be_failure
83
- expect(result.message).to eq("This has failed")
84
- end
85
- end
86
-
87
- describe "by passing the contenxt" do
88
- it "performs a translation" do
89
- result = pass_with(:some_success_reason)
90
-
91
- expect(result).to be_success
92
- expect(result.message).to eq("This has passed")
93
- end
94
- end
95
- end
96
-
97
- describe "passing a Symbol with interpolation variables" do
98
- describe "by failing the context" do
99
- it "performs a translation with interpolation" do
100
- result = fail_with(:failure_with_interpolation,
101
- :reason => "bad account")
102
-
103
- expect(result).to be_failure
104
- expect(result.message).to eq("Failed with bad account")
105
- end
106
- end
107
-
108
- describe "by passing the context" do
109
- it "performs a translation with interpolation" do
110
- result = pass_with(:success_with_interpolation,
111
- :reason => "account in good standing")
112
-
113
- expect(result).to be_success
114
- expect(result.message).to eq("Passed with account in good standing")
115
- end
116
- end
117
- end
118
- end
1
+ require "spec_helper"
2
+ require "test_doubles"
3
+
4
+ class TestsLocalizationAdapter
5
+ extend FunctionalLightService::Organizer
6
+
7
+ def self.call(pass_or_fail, message_or_key, i18n_options = {})
8
+ with(
9
+ :pass_or_fail => pass_or_fail,
10
+ :message_or_key => message_or_key,
11
+ :i18n_options => i18n_options
12
+ ).reduce(TestsLocalizationInvocationOptionsAction)
13
+ end
14
+ end
15
+
16
+ class TestsLocalizationInvocationOptionsAction
17
+ extend FunctionalLightService::Action
18
+
19
+ expects :pass_or_fail, :message_or_key, :i18n_options
20
+
21
+ executed do |context|
22
+ if context.pass_or_fail == true
23
+ context.succeed!(context.message_or_key, context.i18n_options)
24
+ else
25
+ context.fail!(context.message_or_key, context.i18n_options)
26
+ end
27
+ end
28
+ end
29
+
30
+ def pass_with(message_or_key, i18n_options = {})
31
+ TestsLocalizationAdapter.call(true, message_or_key, i18n_options)
32
+ end
33
+
34
+ def fail_with(message_or_key, i18n_options = {})
35
+ TestsLocalizationAdapter.call(false, message_or_key, i18n_options)
36
+ end
37
+
38
+ describe "Localization Adapter" do
39
+ before do
40
+ I18n.backend.store_translations(
41
+ :en,
42
+ :tests_localization_invocation_options_action =>
43
+ {
44
+ :light_service => {
45
+ :failures => {
46
+ :some_failure_reason => "This has failed",
47
+ :failure_with_interpolation => "Failed with %{reason}"
48
+ },
49
+ :successes => {
50
+ :some_success_reason => "This has passed",
51
+ :success_with_interpolation => "Passed with %{reason}"
52
+ }
53
+ }
54
+ }
55
+ )
56
+ end
57
+
58
+ describe "passing a simple string message" do
59
+ describe "by failing the context" do
60
+ it "returns the string" do
61
+ result = fail_with("string message")
62
+
63
+ expect(result).to be_failure
64
+ expect(result.message).to eq("string message")
65
+ end
66
+ end
67
+
68
+ describe "by passing the context" do
69
+ it "returns the string" do
70
+ result = pass_with("string message")
71
+
72
+ expect(result).to be_success
73
+ expect(result.message).to eq("string message")
74
+ end
75
+ end
76
+ end
77
+
78
+ describe "passing a Symbol" do
79
+ describe "by failing the context" do
80
+ it "performs a translation" do
81
+ result = fail_with(:some_failure_reason)
82
+
83
+ expect(result).to be_failure
84
+ expect(result.message).to eq("This has failed")
85
+ end
86
+ end
87
+
88
+ describe "by passing the contenxt" do
89
+ it "performs a translation" do
90
+ result = pass_with(:some_success_reason)
91
+
92
+ expect(result).to be_success
93
+ expect(result.message).to eq("This has passed")
94
+ end
95
+ end
96
+ end
97
+
98
+ describe "passing a Symbol with interpolation variables" do
99
+ describe "by failing the context" do
100
+ it "performs a translation with interpolation" do
101
+ result = fail_with(:failure_with_interpolation,
102
+ :reason => "bad account")
103
+
104
+ expect(result).to be_failure
105
+ expect(result.message).to eq("Failed with bad account")
106
+ end
107
+ end
108
+
109
+ describe "by passing the context" do
110
+ it "performs a translation with interpolation" do
111
+ result = pass_with(:success_with_interpolation,
112
+ :reason => "account in good standing")
113
+
114
+ expect(result).to be_success
115
+ expect(result.message).to eq("Passed with account in good standing")
116
+ end
117
+ end
118
+ end
119
+ end
@@ -1,65 +1,68 @@
1
- require 'spec_helper'
2
- require 'test_doubles'
3
-
4
- RSpec.describe FunctionalLightService::Organizer do
5
- class TestSkipBefore
6
- extend FunctionalLightService::Organizer
7
- def self.call
8
- with(:number => 1)
9
- .reduce([
10
- TestDoubles::SkipAllAction,
11
- reduce_until(->(ctx) { ctx.number == 3 },
12
- TestDoubles::AddsOneAction)
13
- ])
14
- end
15
- end
16
-
17
- class TestSkipAfter
18
- extend FunctionalLightService::Organizer
19
- def self.call
20
- with(:number => 1)
21
- .reduce([
22
- TestDoubles::AddsOneAction,
23
- reduce_until(->(ctx) { ctx.number == 3 }, [
24
- TestDoubles::AddsOneAction
25
- ]),
26
- TestDoubles::SkipAllAction,
27
- TestDoubles::AddsOneAction
28
- ])
29
- end
30
- end
31
-
32
- class TestContextFailure
33
- extend FunctionalLightService::Organizer
34
- def self.call
35
- with(:number => 1)
36
- .reduce([
37
- TestDoubles::FailureAction,
38
- reduce_until(->(ctx) { ctx[:number] == 3 },
39
- TestDoubles::AddsOneAction),
40
- TestDoubles::AddsOneAction
41
- ])
42
- end
43
- end
44
-
45
- it 'skips all the rest of the actions' do
46
- result = TestSkipBefore.call
47
-
48
- expect(result).to be_success
49
- expect(result[:number]).to eq(1)
50
- end
51
-
52
- it 'skips after an action in nested context' do
53
- result = TestSkipAfter.call
54
-
55
- expect(result).to be_success
56
- expect(result[:number]).to eq(3)
57
- end
58
-
59
- it 'respects failure across all nestings' do
60
- result = TestContextFailure.call
61
-
62
- expect(result).to be_failure
63
- expect(result[:number]).to eq(1)
64
- end
65
- end
1
+ require 'spec_helper'
2
+ require 'test_doubles'
3
+
4
+ RSpec.describe FunctionalLightService::Organizer do
5
+ class TestSkipBefore
6
+ extend FunctionalLightService::Organizer
7
+
8
+ def self.call
9
+ with(:number => 1)
10
+ .reduce([
11
+ TestDoubles::SkipAllAction,
12
+ reduce_until(->(ctx) { ctx.number == 3 },
13
+ TestDoubles::AddsOneAction)
14
+ ])
15
+ end
16
+ end
17
+
18
+ class TestSkipAfter
19
+ extend FunctionalLightService::Organizer
20
+
21
+ def self.call
22
+ with(:number => 1)
23
+ .reduce([
24
+ TestDoubles::AddsOneAction,
25
+ reduce_until(->(ctx) { ctx.number == 3 }, [
26
+ TestDoubles::AddsOneAction
27
+ ]),
28
+ TestDoubles::SkipAllAction,
29
+ TestDoubles::AddsOneAction
30
+ ])
31
+ end
32
+ end
33
+
34
+ class TestContextFailure
35
+ extend FunctionalLightService::Organizer
36
+
37
+ def self.call
38
+ with(:number => 1)
39
+ .reduce([
40
+ TestDoubles::FailureAction,
41
+ reduce_until(->(ctx) { ctx[:number] == 3 },
42
+ TestDoubles::AddsOneAction),
43
+ TestDoubles::AddsOneAction
44
+ ])
45
+ end
46
+ end
47
+
48
+ it 'skips all the rest of the actions' do
49
+ result = TestSkipBefore.call
50
+
51
+ expect(result).to be_success
52
+ expect(result[:number]).to eq(1)
53
+ end
54
+
55
+ it 'skips after an action in nested context' do
56
+ result = TestSkipAfter.call
57
+
58
+ expect(result).to be_success
59
+ expect(result[:number]).to eq(3)
60
+ end
61
+
62
+ it 'respects failure across all nestings' do
63
+ result = TestContextFailure.call
64
+
65
+ expect(result).to be_failure
66
+ expect(result[:number]).to eq(1)
67
+ end
68
+ end
@@ -1,89 +1,89 @@
1
- require 'spec_helper'
2
- require 'test_doubles'
3
-
4
- RSpec.describe FunctionalLightService::Organizer do
5
- class TestReduceIf
6
- extend FunctionalLightService::Organizer
7
-
8
- def self.call(context)
9
- with(context).reduce(actions)
10
- end
11
-
12
- def self.actions
13
- [
14
- TestDoubles::AddsOneAction,
15
- reduce_if(->(ctx) { ctx.number == 1 },
16
- TestDoubles::AddsOneAction)
17
- ]
18
- end
19
- end
20
-
21
- let(:empty_context) { FunctionalLightService::Context.make }
22
-
23
- it 'reduces if the block evaluates to true' do
24
- result = TestReduceIf.call(:number => 0)
25
-
26
- expect(result).to be_success
27
- expect(result[:number]).to eq(2)
28
- end
29
-
30
- it 'does not reduce if the block evaluates to false' do
31
- result = TestReduceIf.call(:number => 2)
32
-
33
- expect(result).to be_success
34
- expect(result[:number]).to eq(3)
35
- end
36
-
37
- it 'will not reduce over a failed context' do
38
- empty_context.fail!('Something bad happened')
39
-
40
- result = TestReduceIf.call(empty_context)
41
-
42
- expect(result).to be_failure
43
- end
44
-
45
- it 'does not reduce over a skipped context' do
46
- empty_context.skip_remaining!('No more needed')
47
-
48
- result = TestReduceIf.call(empty_context)
49
- expect(result).to be_success
50
- end
51
-
52
- it "knows that it's being conditionally reduced from within an organizer" do
53
- result = TestReduceIf.call(:number => 2)
54
-
55
- expect(result.organized_by).to eq TestReduceIf
56
- end
57
-
58
- it 'skips actions within in its own scope' do
59
- org = Class.new do
60
- extend FunctionalLightService::Organizer
61
-
62
- def self.call
63
- reduce(actions)
64
- end
65
-
66
- def self.actions
67
- [
68
- reduce_if(
69
- ->(c) { !c.nil? },
70
- [
71
- execute(->(c) { c[:first_reduce_if] = true }),
72
- execute(->(c) { c.skip_remaining! }),
73
- execute(->(c) { c[:second_reduce_if] = true })
74
- ]
75
- ),
76
- execute(->(c) { c[:last_outside] = true })
77
- ]
78
- end
79
- end
80
-
81
- result = org.call
82
-
83
- aggregate_failures do
84
- expect(result[:first_reduce_if]).to be true
85
- expect(result[:second_reduce_if]).to be_nil
86
- expect(result[:last_outside]).to be true
87
- end
88
- end
89
- end
1
+ require 'spec_helper'
2
+ require 'test_doubles'
3
+
4
+ RSpec.describe FunctionalLightService::Organizer do
5
+ class TestReduceIf
6
+ extend FunctionalLightService::Organizer
7
+
8
+ def self.call(context)
9
+ with(context).reduce(actions)
10
+ end
11
+
12
+ def self.actions
13
+ [
14
+ TestDoubles::AddsOneAction,
15
+ reduce_if(->(ctx) { ctx.number == 1 },
16
+ TestDoubles::AddsOneAction)
17
+ ]
18
+ end
19
+ end
20
+
21
+ let(:empty_context) { FunctionalLightService::Context.make }
22
+
23
+ it 'reduces if the block evaluates to true' do
24
+ result = TestReduceIf.call(:number => 0)
25
+
26
+ expect(result).to be_success
27
+ expect(result[:number]).to eq(2)
28
+ end
29
+
30
+ it 'does not reduce if the block evaluates to false' do
31
+ result = TestReduceIf.call(:number => 2)
32
+
33
+ expect(result).to be_success
34
+ expect(result[:number]).to eq(3)
35
+ end
36
+
37
+ it 'will not reduce over a failed context' do
38
+ empty_context.fail!('Something bad happened')
39
+
40
+ result = TestReduceIf.call(empty_context)
41
+
42
+ expect(result).to be_failure
43
+ end
44
+
45
+ it 'does not reduce over a skipped context' do
46
+ empty_context.skip_remaining!('No more needed')
47
+
48
+ result = TestReduceIf.call(empty_context)
49
+ expect(result).to be_success
50
+ end
51
+
52
+ it "knows that it's being conditionally reduced from within an organizer" do
53
+ result = TestReduceIf.call(:number => 2)
54
+
55
+ expect(result.organized_by).to eq TestReduceIf
56
+ end
57
+
58
+ it 'skips actions within in its own scope' do
59
+ org = Class.new do
60
+ extend FunctionalLightService::Organizer
61
+
62
+ def self.call
63
+ reduce(actions)
64
+ end
65
+
66
+ def self.actions
67
+ [
68
+ reduce_if(
69
+ ->(c) { !c.nil? },
70
+ [
71
+ execute(->(c) { c[:first_reduce_if] = true }),
72
+ execute(->(c) { c.skip_remaining! }), # rubocop:disable Style/SymbolProc
73
+ execute(->(c) { c[:second_reduce_if] = true })
74
+ ]
75
+ ),
76
+ execute(->(c) { c[:last_outside] = true })
77
+ ]
78
+ end
79
+ end
80
+
81
+ result = org.call
82
+
83
+ aggregate_failures do
84
+ expect(result[:first_reduce_if]).to be true
85
+ expect(result[:second_reduce_if]).to be_nil
86
+ expect(result[:last_outside]).to be true
87
+ end
88
+ end
89
+ end