switchyard 7.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 (129) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/project-build.yml +71 -0
  3. data/.gitignore +24 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +101 -0
  6. data/.solargraph.yml +11 -0
  7. data/Appraisals +7 -0
  8. data/CHANGELOG.md +303 -0
  9. data/CODE_OF_CONDUCT.md +22 -0
  10. data/Gemfile +4 -0
  11. data/LICENSE +22 -0
  12. data/README.md +1663 -0
  13. data/Rakefile +12 -0
  14. data/VERSION +1 -0
  15. data/audit/bench.rb +99 -0
  16. data/audit/verify_findings.rb +172 -0
  17. data/gemfiles/dry_inflector_0_2_1.gemfile +5 -0
  18. data/gemfiles/i18n_1_8_11.gemfile +5 -0
  19. data/lib/switchyard/action.rb +145 -0
  20. data/lib/switchyard/configuration.rb +36 -0
  21. data/lib/switchyard/context/key_verifier.rb +124 -0
  22. data/lib/switchyard/context.rb +208 -0
  23. data/lib/switchyard/deprecations.rb +26 -0
  24. data/lib/switchyard/errors.rb +9 -0
  25. data/lib/switchyard/functional/enum.rb +286 -0
  26. data/lib/switchyard/functional/maybe.rb +21 -0
  27. data/lib/switchyard/functional/monad.rb +77 -0
  28. data/lib/switchyard/functional/null.rb +88 -0
  29. data/lib/switchyard/functional/option.rb +100 -0
  30. data/lib/switchyard/functional/result.rb +129 -0
  31. data/lib/switchyard/functional/sequencer.rb +144 -0
  32. data/lib/switchyard/i18n/localization_adapter.rb +50 -0
  33. data/lib/switchyard/localization_adapter.rb +39 -0
  34. data/lib/switchyard/localization_map.rb +9 -0
  35. data/lib/switchyard/organizer/execute.rb +16 -0
  36. data/lib/switchyard/organizer/iterate.rb +30 -0
  37. data/lib/switchyard/organizer/reduce_case.rb +50 -0
  38. data/lib/switchyard/organizer/reduce_if.rb +19 -0
  39. data/lib/switchyard/organizer/reduce_if_else.rb +23 -0
  40. data/lib/switchyard/organizer/reduce_until.rb +22 -0
  41. data/lib/switchyard/organizer/reduce_while.rb +31 -0
  42. data/lib/switchyard/organizer/scoped_reducable.rb +15 -0
  43. data/lib/switchyard/organizer/with_callback.rb +28 -0
  44. data/lib/switchyard/organizer/with_reducer.rb +81 -0
  45. data/lib/switchyard/organizer/with_reducer_factory.rb +20 -0
  46. data/lib/switchyard/organizer/with_reducer_log_decorator.rb +111 -0
  47. data/lib/switchyard/organizer.rb +129 -0
  48. data/lib/switchyard/testing/context_factory.rb +48 -0
  49. data/lib/switchyard/testing.rb +3 -0
  50. data/lib/switchyard/version.rb +5 -0
  51. data/lib/switchyard.rb +36 -0
  52. data/resources/fail_actions.png +0 -0
  53. data/resources/light-service.png +0 -0
  54. data/resources/organizer_and_actions.png +0 -0
  55. data/resources/skip_actions.png +0 -0
  56. data/spec/acceptance/add_numbers_spec.rb +11 -0
  57. data/spec/acceptance/after_actions_spec.rb +87 -0
  58. data/spec/acceptance/around_each_spec.rb +19 -0
  59. data/spec/acceptance/before_actions_spec.rb +115 -0
  60. data/spec/acceptance/custom_log_from_organizer_spec.rb +61 -0
  61. data/spec/acceptance/deprecation_warnings_spec.rb +82 -0
  62. data/spec/acceptance/fail_spec.rb +52 -0
  63. data/spec/acceptance/log_from_organizer_spec.rb +154 -0
  64. data/spec/acceptance/message_localization_spec.rb +119 -0
  65. data/spec/acceptance/organizer/add_aliases_spec.rb +28 -0
  66. data/spec/acceptance/organizer/add_to_context_spec.rb +54 -0
  67. data/spec/acceptance/organizer/around_each_with_reduce_if_spec.rb +42 -0
  68. data/spec/acceptance/organizer/context_failure_and_skipping_spec.rb +90 -0
  69. data/spec/acceptance/organizer/execute_spec.rb +67 -0
  70. data/spec/acceptance/organizer/iterate_spec.rb +44 -0
  71. data/spec/acceptance/organizer/reduce_case_spec.rb +65 -0
  72. data/spec/acceptance/organizer/reduce_if_else_spec.rb +60 -0
  73. data/spec/acceptance/organizer/reduce_if_spec.rb +89 -0
  74. data/spec/acceptance/organizer/reduce_until_spec.rb +49 -0
  75. data/spec/acceptance/organizer/reduce_while_spec.rb +96 -0
  76. data/spec/acceptance/organizer/with_callback_spec.rb +113 -0
  77. data/spec/acceptance/organizer_entry_point_spec.rb +35 -0
  78. data/spec/acceptance/rollback_spec.rb +183 -0
  79. data/spec/acceptance/skip_all_remaining_spec.rb +139 -0
  80. data/spec/acceptance/testing/context_factory_spec.rb +54 -0
  81. data/spec/action_expected_keys_spec.rb +63 -0
  82. data/spec/action_expects_and_promises_spec.rb +97 -0
  83. data/spec/action_optional_expected_keys_spec.rb +107 -0
  84. data/spec/action_promised_keys_spec.rb +126 -0
  85. data/spec/action_spec.rb +97 -0
  86. data/spec/context/inspect_spec.rb +52 -0
  87. data/spec/context_spec.rb +301 -0
  88. data/spec/examples/amount_spec.rb +77 -0
  89. data/spec/examples/controller_spec.rb +63 -0
  90. data/spec/examples/validate_address_spec.rb +38 -0
  91. data/spec/i18n_localization_adapter_spec.rb +83 -0
  92. data/spec/lib/deterministic/class_mixin_spec.rb +24 -0
  93. data/spec/lib/deterministic/currify_spec.rb +90 -0
  94. data/spec/lib/deterministic/monad_axioms.rb +46 -0
  95. data/spec/lib/deterministic/monad_spec.rb +47 -0
  96. data/spec/lib/deterministic/null_spec.rb +65 -0
  97. data/spec/lib/deterministic/option_spec.rb +140 -0
  98. data/spec/lib/deterministic/result/failure_spec.rb +65 -0
  99. data/spec/lib/deterministic/result/result_map_spec.rb +155 -0
  100. data/spec/lib/deterministic/result/result_shared.rb +25 -0
  101. data/spec/lib/deterministic/result/success_spec.rb +41 -0
  102. data/spec/lib/deterministic/result_spec.rb +63 -0
  103. data/spec/lib/deterministic/sequencer_spec.rb +506 -0
  104. data/spec/lib/edge_cases_spec.rb +156 -0
  105. data/spec/lib/enum_spec.rb +114 -0
  106. data/spec/lib/native_pattern_matching_spec.rb +74 -0
  107. data/spec/localization_adapter_spec.rb +66 -0
  108. data/spec/organizer/with_reducer_spec.rb +56 -0
  109. data/spec/organizer_key_aliases_spec.rb +29 -0
  110. data/spec/organizer_spec.rb +115 -0
  111. data/spec/readme_spec.rb +45 -0
  112. data/spec/sample/calculates_order_tax_action_spec.rb +16 -0
  113. data/spec/sample/calculates_tax_spec.rb +30 -0
  114. data/spec/sample/looks_up_tax_percentage_action_spec.rb +55 -0
  115. data/spec/sample/provides_free_shipping_action_spec.rb +25 -0
  116. data/spec/sample/tax/calculates_order_tax_action.rb +10 -0
  117. data/spec/sample/tax/calculates_tax.rb +11 -0
  118. data/spec/sample/tax/looks_up_tax_percentage_action.rb +28 -0
  119. data/spec/sample/tax/provides_free_shipping_action.rb +11 -0
  120. data/spec/spec_helper.rb +32 -0
  121. data/spec/support.rb +1 -0
  122. data/spec/test_doubles.rb +656 -0
  123. data/spec/testing/context_factory/iterate_spec.rb +39 -0
  124. data/spec/testing/context_factory/reduce_if_spec.rb +40 -0
  125. data/spec/testing/context_factory/reduce_until_spec.rb +40 -0
  126. data/spec/testing/context_factory/with_callback_spec.rb +38 -0
  127. data/spec/testing/context_factory_spec.rb +76 -0
  128. data/switchyard.gemspec +35 -0
  129. metadata +351 -0
@@ -0,0 +1,65 @@
1
+ require 'spec_helper'
2
+ require_relative '../monad_axioms'
3
+ require_relative 'result_shared'
4
+
5
+ describe Switchyard::Result::Failure do
6
+ include Switchyard::Prelude::Result
7
+
8
+ it_behaves_like 'a Monad' do
9
+ let(:monad) { described_class }
10
+ end
11
+
12
+ subject { described_class.new(1) }
13
+
14
+ specify { expect(subject).to be_an_instance_of described_class }
15
+ specify { expect(subject).to be_failure }
16
+ specify { expect(subject).not_to be_success }
17
+ specify { expect(subject.success?).to be_falsey }
18
+ specify { expect(subject.failure?).to be_truthy }
19
+
20
+ specify { expect(subject).to be_an_instance_of described_class }
21
+ specify { expect(subject).to eq(described_class.new(1)) }
22
+ specify { expect(subject.fmap { |v| v + 1 }).to eq Failure(2) }
23
+ specify { expect(subject.map { |v| Success(v + 1) }).to eq Failure(1) }
24
+ specify { expect(subject.map_err { |v| Success(v + 1) }).to eq Success(2) }
25
+ specify do
26
+ expect(subject.pipe { |v| raise RuntimeError unless v == Failure(1) }).to eq Failure(1)
27
+ end
28
+
29
+ specify { expect(subject.or(Success(2))).to eq Success(2) }
30
+ specify { expect(subject.or(Failure(2))).to eq Failure(2) }
31
+ specify { expect(subject.or_else { Success(2) }).to eq Success(2) }
32
+ specify { expect(subject.or_else { Failure(2) }).to eq Failure(2) }
33
+
34
+ specify { expect(subject.and(Success(2))).to eq Failure(1) }
35
+ specify { expect(subject.and_then { Success(2) }).to eq Failure(1) }
36
+
37
+ it_behaves_like 'Result' do
38
+ let(:result) { described_class }
39
+ end
40
+
41
+ it "#or" do
42
+ expect(Success(1).or(Failure(2))).to eq Success(1)
43
+ expect(Failure(1).or(Success(2))).to eq Success(2)
44
+ expect { Failure(1).or(2) }.to raise_error(Switchyard::Monad::NotMonadError)
45
+ end
46
+
47
+ it "#or_else" do
48
+ expect(Success(1).or_else { Failure(2) }).to eq Success(1)
49
+ expect(Failure(1).or_else { |v| Success(v + 1) }).to eq Success(2)
50
+ expect { Failure(1).or_else { 2 } }.to raise_error(Switchyard::Monad::NotMonadError)
51
+ end
52
+
53
+ it "#and" do
54
+ expect(Success(1).and(Success(2))).to eq Success(2)
55
+ expect(Failure(1).and(Success(2))).to eq Failure(1)
56
+ expect { Success(1).and(2) }.to raise_error(Switchyard::Monad::NotMonadError)
57
+ end
58
+
59
+ it "#and_then" do
60
+ expect(Success(1).and_then { Success(2) }).to eq Success(2)
61
+ expect(Failure(1).and_then { Success(2) }).to eq Failure(1)
62
+ expect { Success(1).and_then { 2 } }.to \
63
+ raise_error(Switchyard::Monad::NotMonadError)
64
+ end
65
+ end
@@ -0,0 +1,155 @@
1
+ require 'spec_helper'
2
+
3
+ describe Switchyard::Result do
4
+ include Switchyard::Prelude::Result
5
+
6
+ context ">> (map)" do
7
+ specify { expect(Success(0).map { |n| Success(n + 1) }).to eq Success(1) }
8
+ specify { expect(Failure(0).map { |n| Success(n + 1) }).to eq Failure(0) }
9
+
10
+ it "Failure stops execution" do
11
+ class ChainUnderTest
12
+ include Switchyard::Prelude::Result
13
+
14
+ alias :m :method
15
+
16
+ def call
17
+ init >>
18
+ m(:validate) >>
19
+ m(:send) >>
20
+ m(:parse)
21
+ end
22
+
23
+ def init
24
+ Success(:step => 1)
25
+ end
26
+
27
+ def validate(i)
28
+ i[:step] = i[:step] + 1
29
+ Success(i)
30
+ end
31
+
32
+ def send(i)
33
+ i[:step] = i[:step] + 1
34
+ Failure("Error @ #{i.fetch(:step)}")
35
+ end
36
+
37
+ def parse(i)
38
+ i[:step] = i[:step] + 1
39
+ Success(i)
40
+ end
41
+ end
42
+
43
+ test = ChainUnderTest.new
44
+
45
+ expect(test.call).to eq Failure("Error @ 3")
46
+ end
47
+
48
+ it "expects an Result" do
49
+ def returns_non_result(_i)
50
+ 2
51
+ end
52
+
53
+ expect { Success(1) >> method(:returns_non_result) }.to \
54
+ raise_error(Switchyard::Monad::NotMonadError)
55
+ end
56
+
57
+ it "works with a block" do
58
+ expect(
59
+ Success(1).map { |i| Success(i + 1) }
60
+ ).to eq Success(2)
61
+ end
62
+
63
+ it "works with a lambda" do
64
+ expect(
65
+ Success(1) >> ->(i) { Success(i + 1) }
66
+ ).to eq Success(2)
67
+ end
68
+
69
+ it "does not catch exceptions" do
70
+ expect do
71
+ Success(1) >> ->(_i) { raise "error" }
72
+ end.to raise_error(RuntimeError)
73
+ end
74
+ end
75
+
76
+ context "using self as the context for success" do
77
+ class SelfContextUnderTest
78
+ include Switchyard::Prelude::Result
79
+
80
+ def call
81
+ @step = 0
82
+ Success(self)
83
+ .map(&:validate)
84
+ .map(&:build)
85
+ .map(&:send)
86
+ end
87
+
88
+ def validate
89
+ @step = 1
90
+ Success(self)
91
+ end
92
+
93
+ def build
94
+ @step = 2
95
+ Success(self)
96
+ end
97
+
98
+ def send
99
+ @step = 3
100
+ Success(self)
101
+ end
102
+
103
+ def inspect
104
+ "Step #{@step}"
105
+ end
106
+
107
+ # # def self.procify(*meths)
108
+ # # meths.each do |m|
109
+ # # new_m = "__#{m}__procified".to_sym
110
+ # # alias new_m m
111
+ # # define_method new_m do |ctx|
112
+ # # method(m)
113
+ # # end
114
+ # # end
115
+ # # end
116
+
117
+ # procify :send
118
+ end
119
+
120
+ it "works" do
121
+ test = SelfContextUnderTest.new.call
122
+ expect(test).to be_a described_class::Success
123
+ expect(test.inspect).to eq "Success(Step 3)"
124
+ end
125
+ end
126
+
127
+ context "<< (pipe)" do
128
+ it "ignores the output of pipe" do
129
+ acc = "ctx: "
130
+ log = ->(ctx) { acc += ctx.inspect }
131
+
132
+ actual = Success(1).pipe(log).map { Success(2) }
133
+ expect(actual).to eq Success(2)
134
+ expect(acc).to eq "ctx: Success(1)"
135
+ end
136
+
137
+ it "works with <<" do
138
+ log = ->(n) { n.value + 1 }
139
+ foo = ->(n) { Success(n + 1) }
140
+
141
+ Success(1) << log >> foo
142
+ end
143
+ end
144
+
145
+ context ">= (try)" do
146
+ it "try (>=) catches errors and wraps them as Failure" do
147
+ def error(ctx)
148
+ raise "error #{ctx}"
149
+ end
150
+
151
+ actual = Success(1) >= method(:error)
152
+ expect(actual.inspect).to eq "Failure(#<RuntimeError: error 1>)"
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,25 @@
1
+ shared_examples 'Result' do
2
+ let(:result_name) { described_class.name.split("::")[-1] }
3
+ specify { expect(subject.value).to eq 1 }
4
+ specify { expect(result.new(subject)).to eq result.new(1) }
5
+
6
+ it "#fmap" do
7
+ expect(result.new(1).fmap { |e| e + 1 }).to eq result.new(2)
8
+ end
9
+
10
+ it "#bind" do
11
+ expect(result.new(1).bind { |v| result.new(v + 1) }).to eq result.new(2)
12
+ end
13
+
14
+ it "#to_s" do
15
+ expect(result.new(1).to_s).to eq "1"
16
+ # il formato di Hash#to_s/inspect cambia tra Ruby 3.3 e 3.4: costruiamo l'atteso dinamicamente
17
+ expect(result.new(:a => 1).to_s).to eq({ :a => 1 }.to_s)
18
+ end
19
+
20
+ it "#inspect" do
21
+ expect(result.new(1).inspect).to eq "#{result_name}(1)"
22
+ expect(result.new(:a => 1).inspect).to eq "#{result_name}(#{{ :a => 1 }.inspect})"
23
+ end
24
+ end
25
+
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+ require_relative '../monad_axioms'
3
+ require_relative 'result_shared'
4
+
5
+ describe Switchyard::Result::Success do
6
+ include Switchyard::Prelude::Result
7
+
8
+ it_behaves_like 'a Monad' do
9
+ let(:monad) { described_class }
10
+ end
11
+
12
+ subject { described_class.new(1) }
13
+
14
+ specify { expect(subject).to be_an_instance_of described_class }
15
+ specify { expect(subject).to be_success }
16
+ specify { expect(subject).not_to be_failure }
17
+ specify { expect(subject.success?).to be_truthy }
18
+ specify { expect(subject.failure?).to be_falsey }
19
+
20
+ specify { expect(subject).to be_an_instance_of described_class }
21
+ specify { expect(subject).to eq(described_class.new(1)) }
22
+ specify { expect(subject.fmap { |v| v + 1 }).to eq Success(2) }
23
+ specify { expect(subject.map { |v| Failure(v + 1) }).to eq Failure(2) }
24
+ specify { expect(subject.map_err { |v| Failure(v + 1) }).to eq Success(1) }
25
+
26
+ specify do
27
+ expect(subject.pipe { |r| raise RuntimeError unless r == Success(1) }).to eq Success(1)
28
+ end
29
+
30
+ specify { expect(subject.or(Success(2))).to eq Success(1) }
31
+ specify { expect(subject.or_else { Success(2) }).to eq Success(1) }
32
+
33
+ specify { expect(subject.and(Success(2))).to eq Success(2) }
34
+ specify { expect(subject.and(Failure(2))).to eq Failure(2) }
35
+ specify { expect(subject.and_then { Success(2) }).to eq Success(2) }
36
+ specify { expect(subject.and_then { Failure(2) }).to eq Failure(2) }
37
+
38
+ it_behaves_like 'Result' do
39
+ let(:result) { described_class }
40
+ end
41
+ end
@@ -0,0 +1,63 @@
1
+ require 'spec_helper'
2
+
3
+ describe Switchyard::Result do
4
+ include Switchyard::Prelude::Result
5
+
6
+ it "can't call Result#new directly" do
7
+ # il formato del messaggio NoMethodError cambia tra le versioni di Ruby
8
+ expect { described_class.new(1) }.to raise_error(NoMethodError, /private method [`']new'/)
9
+ end
10
+
11
+ it "fmap" do
12
+ expect(Success(1).fmap { |n| n + 1 }).to eq Success(2)
13
+ expect(Failure(0).fmap { |n| n + 1 }).to eq Failure(1)
14
+ end
15
+
16
+ it "map" do
17
+ expect(Success(1).map { |n| Success(n + 1) }).to eq Success(2)
18
+ expect(Failure(0).map { |n| Success(n + 1) }).to eq Failure(0)
19
+ end
20
+
21
+ it "+" do
22
+ expect(Success([1]) + Failure([2])).to eq Failure([2])
23
+ expect(Success(1) + Success(1)).to eq Success(2)
24
+ expect(Failure(2) + Success(1)).to eq Failure(2)
25
+ expect(Failure([2]) + Failure([3]) + Success(1)).to eq Failure([2, 3])
26
+ expect(Success([1]) + Success([1])).to eq Success([1, 1])
27
+ expect { Success([1]) + Success(1) }.to raise_error TypeError
28
+ end
29
+
30
+ subject { Success(1) }
31
+ # specify { expect(subject).to be_an_instance_of described_class }
32
+ specify { expect(subject).to be_success }
33
+ specify { expect(subject).not_to be_failure }
34
+ specify { expect(subject.success?).to be_truthy }
35
+ specify { expect(subject.failure?).to be_falsey }
36
+
37
+ specify { expect(subject).to be_a described_class }
38
+ # specify { expect(subject).to eq(described_class.new(1)) }
39
+ specify { expect(subject.fmap { |v| v + 1 }).to eq Success(2) }
40
+ specify { expect(subject.map { |v| Failure(v + 1) }).to eq Failure(2) }
41
+ specify { expect(subject.map_err { |v| Failure(v + 1) }).to eq Success(1) }
42
+
43
+ specify do
44
+ expect(subject.pipe do |r|
45
+ raise RuntimeError unless r == Success(1)
46
+ end).to eq Success(1)
47
+ end
48
+
49
+ specify { expect(subject.or(Success(2))).to eq Success(1) }
50
+ specify { expect(subject.or_else { Success(2) }).to eq Success(1) }
51
+
52
+ specify { expect(subject.and(Success(2))).to eq Success(2) }
53
+ specify { expect(subject.and(Failure(2))).to eq Failure(2) }
54
+ specify { expect(subject.and_then { Success(2) }).to eq Success(2) }
55
+ specify { expect(subject.and_then { Failure(2) }).to eq Failure(2) }
56
+
57
+ it "try!" do
58
+ expect(described_class.try! { 1 }).to eq Success(1)
59
+ expect(described_class.try! { raise "error" }.inspect).to \
60
+ eq Failure(RuntimeError.new("error")).inspect
61
+ end
62
+ end
63
+