functional-light-service 0.2.4

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 (110) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +72 -0
  5. data/.travis.yml +22 -0
  6. data/Appraisals +3 -0
  7. data/CHANGELOG.md +37 -0
  8. data/CODE_OF_CONDUCT.md +22 -0
  9. data/Gemfile +6 -0
  10. data/LICENSE +22 -0
  11. data/README.md +1424 -0
  12. data/Rakefile +12 -0
  13. data/VERSION +1 -0
  14. data/functional-light-service.gemspec +26 -0
  15. data/gemfiles/activesupport_5.gemfile +8 -0
  16. data/gemfiles/activesupport_5.gemfile.lock +82 -0
  17. data/lib/functional-light-service/action.rb +102 -0
  18. data/lib/functional-light-service/configuration.rb +24 -0
  19. data/lib/functional-light-service/context/key_verifier.rb +118 -0
  20. data/lib/functional-light-service/context.rb +165 -0
  21. data/lib/functional-light-service/errors.rb +6 -0
  22. data/lib/functional-light-service/functional/enum.rb +254 -0
  23. data/lib/functional-light-service/functional/maybe.rb +14 -0
  24. data/lib/functional-light-service/functional/monad.rb +66 -0
  25. data/lib/functional-light-service/functional/null.rb +74 -0
  26. data/lib/functional-light-service/functional/option.rb +99 -0
  27. data/lib/functional-light-service/functional/result.rb +122 -0
  28. data/lib/functional-light-service/localization_adapter.rb +44 -0
  29. data/lib/functional-light-service/organizer/execute.rb +14 -0
  30. data/lib/functional-light-service/organizer/iterate.rb +22 -0
  31. data/lib/functional-light-service/organizer/reduce_if.rb +17 -0
  32. data/lib/functional-light-service/organizer/reduce_until.rb +20 -0
  33. data/lib/functional-light-service/organizer/scoped_reducable.rb +13 -0
  34. data/lib/functional-light-service/organizer/verify_call_method_exists.rb +28 -0
  35. data/lib/functional-light-service/organizer/with_callback.rb +26 -0
  36. data/lib/functional-light-service/organizer/with_reducer.rb +71 -0
  37. data/lib/functional-light-service/organizer/with_reducer_factory.rb +18 -0
  38. data/lib/functional-light-service/organizer/with_reducer_log_decorator.rb +105 -0
  39. data/lib/functional-light-service/organizer.rb +105 -0
  40. data/lib/functional-light-service/testing/context_factory.rb +40 -0
  41. data/lib/functional-light-service/testing.rb +1 -0
  42. data/lib/functional-light-service/version.rb +3 -0
  43. data/lib/functional-light-service.rb +29 -0
  44. data/resources/fail_actions.png +0 -0
  45. data/resources/light-service.png +0 -0
  46. data/resources/organizer_and_actions.png +0 -0
  47. data/resources/skip_actions.png +0 -0
  48. data/spec/acceptance/add_numbers_spec.rb +11 -0
  49. data/spec/acceptance/after_actions_spec.rb +71 -0
  50. data/spec/acceptance/around_each_spec.rb +19 -0
  51. data/spec/acceptance/before_actions_spec.rb +98 -0
  52. data/spec/acceptance/custom_log_from_organizer_spec.rb +60 -0
  53. data/spec/acceptance/fail_spec.rb +24 -0
  54. data/spec/acceptance/include_warning_spec.rb +29 -0
  55. data/spec/acceptance/log_from_organizer_spec.rb +154 -0
  56. data/spec/acceptance/message_localization_spec.rb +118 -0
  57. data/spec/acceptance/not_having_call_method_warning_spec.rb +39 -0
  58. data/spec/acceptance/organizer/around_each_with_reduce_if_spec.rb +42 -0
  59. data/spec/acceptance/organizer/context_failure_and_skipping_spec.rb +65 -0
  60. data/spec/acceptance/organizer/execute_spec.rb +46 -0
  61. data/spec/acceptance/organizer/iterate_spec.rb +37 -0
  62. data/spec/acceptance/organizer/reduce_if_spec.rb +51 -0
  63. data/spec/acceptance/organizer/reduce_until_spec.rb +43 -0
  64. data/spec/acceptance/organizer/with_callback_spec.rb +110 -0
  65. data/spec/acceptance/rollback_spec.rb +132 -0
  66. data/spec/acceptance/skip_all_warning_spec.rb +20 -0
  67. data/spec/acceptance/testing/context_factory_spec.rb +54 -0
  68. data/spec/action_expected_keys_spec.rb +63 -0
  69. data/spec/action_expects_and_promises_spec.rb +93 -0
  70. data/spec/action_promised_keys_spec.rb +122 -0
  71. data/spec/action_spec.rb +89 -0
  72. data/spec/context/inspect_spec.rb +57 -0
  73. data/spec/context_spec.rb +197 -0
  74. data/spec/examples/amount_spec.rb +77 -0
  75. data/spec/examples/controller_spec.rb +63 -0
  76. data/spec/examples/validate_address_spec.rb +37 -0
  77. data/spec/lib/deterministic/class_mixin_spec.rb +24 -0
  78. data/spec/lib/deterministic/currify_spec.rb +88 -0
  79. data/spec/lib/deterministic/monad_axioms.rb +44 -0
  80. data/spec/lib/deterministic/monad_spec.rb +45 -0
  81. data/spec/lib/deterministic/null_spec.rb +58 -0
  82. data/spec/lib/deterministic/option_spec.rb +133 -0
  83. data/spec/lib/deterministic/result/failure_spec.rb +65 -0
  84. data/spec/lib/deterministic/result/result_map_spec.rb +154 -0
  85. data/spec/lib/deterministic/result/result_shared.rb +24 -0
  86. data/spec/lib/deterministic/result/success_spec.rb +41 -0
  87. data/spec/lib/deterministic/result_spec.rb +63 -0
  88. data/spec/lib/enum_spec.rb +112 -0
  89. data/spec/localization_adapter_spec.rb +83 -0
  90. data/spec/organizer/with_reducer_spec.rb +56 -0
  91. data/spec/organizer_key_aliases_spec.rb +29 -0
  92. data/spec/organizer_spec.rb +93 -0
  93. data/spec/readme_spec.rb +47 -0
  94. data/spec/sample/calculates_order_tax_action_spec.rb +16 -0
  95. data/spec/sample/calculates_tax_spec.rb +30 -0
  96. data/spec/sample/looks_up_tax_percentage_action_spec.rb +53 -0
  97. data/spec/sample/provides_free_shipping_action_spec.rb +25 -0
  98. data/spec/sample/tax/calculates_order_tax_action.rb +9 -0
  99. data/spec/sample/tax/calculates_tax.rb +11 -0
  100. data/spec/sample/tax/looks_up_tax_percentage_action.rb +27 -0
  101. data/spec/sample/tax/provides_free_shipping_action.rb +10 -0
  102. data/spec/spec_helper.rb +24 -0
  103. data/spec/support.rb +1 -0
  104. data/spec/test_doubles.rb +552 -0
  105. data/spec/testing/context_factory/iterate_spec.rb +39 -0
  106. data/spec/testing/context_factory/reduce_if_spec.rb +40 -0
  107. data/spec/testing/context_factory/reduce_until_spec.rb +40 -0
  108. data/spec/testing/context_factory/with_callback_spec.rb +38 -0
  109. data/spec/testing/context_factory_spec.rb +55 -0
  110. metadata +285 -0
@@ -0,0 +1,88 @@
1
+ require 'spec_helper'
2
+ require 'pry'
3
+
4
+ module FunctionalLightService
5
+ module Currify
6
+ module ClassMethods
7
+ def currify(*names)
8
+ names.each do |name|
9
+ unbound_method = instance_method(name)
10
+
11
+ define_method(name) do |*args|
12
+ curried_method = unbound_method.bind(self).to_proc.curry
13
+ curried_method[*args]
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ def self.included(curried)
20
+ curried.extend ClassMethods
21
+ end
22
+ end
23
+ end
24
+
25
+ class Object
26
+ class Proc
27
+ def self.compose(f, g)
28
+ ->(*args) { f[g[*args]] }
29
+ end
30
+
31
+ # rubocop:disable Naming/BinaryOperatorParameterName
32
+ # Compose left to right
33
+ def |(g)
34
+ Proc.compose(g, self)
35
+ end
36
+
37
+ # Compose right to left
38
+ def *(g)
39
+ Proc.compose(self, g)
40
+ end
41
+ # rubocop:enable Naming/BinaryOperatorParameterName
42
+ end
43
+ end
44
+
45
+ class Booking
46
+ include FunctionalLightService::Currify
47
+ include FunctionalLightService::Prelude::Result
48
+
49
+ def initialize(deps)
50
+ @deps = deps
51
+ end
52
+
53
+ def build(id, format)
54
+ validate(id) | req | find | render(format)
55
+
56
+ validate(id) | request(id) | find
57
+ end
58
+
59
+ def validate(id)
60
+ Success(id)
61
+ end
62
+
63
+ def req(a, id)
64
+ Success(:id => id + a)
65
+ end
66
+
67
+ def find(req)
68
+ Success(:found => req)
69
+ end
70
+
71
+ def render(format, req)
72
+ Success("rendered in #{format}: #{req[:found]}")
73
+ end
74
+
75
+ currify :find, :render, :req
76
+ end
77
+
78
+ describe "Pref" do
79
+ include FunctionalLightService::Prelude::Result
80
+
81
+ it "does something" do
82
+ b = Booking.new(1)
83
+ actual = b.validate(1) >> b.req(2) >> b.find >> b.render(:html)
84
+
85
+ expected = FunctionalLightService::Result::Success.new("rendered in html: {:id=>3}")
86
+ expect(actual).to eq expected
87
+ end
88
+ end
@@ -0,0 +1,44 @@
1
+ shared_examples 'a Monad' do
2
+ describe 'axioms' do
3
+ it '1st monadic law: left-identity' do
4
+ f = ->(value) { monad.new(value + 1) }
5
+ expect(
6
+ monad.new(1).bind do |value|
7
+ f.call(value)
8
+ end
9
+ ).to eq f.call(1)
10
+ end
11
+
12
+ it '2nd monadic law: right-identy - new and bind do not change the value' do
13
+ expect(
14
+ monad.new(1).bind do |value|
15
+ monad.new(value)
16
+ end
17
+ ).to eq monad.new(1)
18
+ end
19
+
20
+ it '3rd monadic law: associativity' do
21
+ f = ->(value) { monad.new(value + 1) }
22
+ g = ->(value) { monad.new(value + 100) }
23
+
24
+ id1 = monad.new(1).bind { |a| f.call(a) }.bind { |b| g.call(b) }
25
+
26
+ id2 = monad.new(1).bind do |a|
27
+ f.call(a).bind do |b|
28
+ g.call(b)
29
+ end
30
+ end
31
+
32
+ expect(id1).to eq id2
33
+ end
34
+
35
+ it '#bind must return a monad' do
36
+ expect(monad.new(1).bind { |v| monad.new(v) }).to eq monad.new(1)
37
+ expect { monad.new(1).bind {} }.to raise_error(FunctionalLightService::Monad::NotMonadError)
38
+ end
39
+
40
+ it '#new must return a monad' do
41
+ expect(monad.new(1)).to be_a monad
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+ require_relative 'monad_axioms'
3
+
4
+ describe FunctionalLightService::Monad do
5
+ class Identity
6
+ include FunctionalLightService::Monad
7
+ end
8
+
9
+ let(:monad) { Identity }
10
+ it_behaves_like 'a Monad' do
11
+ # let(:monad) { monad }
12
+ end
13
+
14
+ specify { expect(Identity.new(1).inspect).to eq 'Identity(1)' }
15
+ specify { expect(Identity.new(1).to_s).to eq '1' }
16
+ specify { expect(Identity.new(nil).inspect).to eq 'Identity(nil)' }
17
+ specify { expect(Identity.new(nil).to_s).to eq '' }
18
+ specify { expect(Identity.new([1, 2]).fmap(&:to_s)).to eq Identity.new("[1, 2]") }
19
+ specify { expect(Identity.new(1).fmap { |v| v + 2 }).to eq Identity.new(3) }
20
+ specify { expect(Identity.new('foo').fmap(&:upcase)).to eq Identity.new('FOO') }
21
+
22
+ context '#bind' do
23
+ it "raises an error if the passed function does not return a monad of the same class" do
24
+ expect { Identity.new(1).bind {} }.to \
25
+ raise_error(FunctionalLightService::Monad::NotMonadError)
26
+ end
27
+ specify { expect(Identity.new(1).bind { |value| Identity.new(value) }).to eq Identity.new(1) }
28
+
29
+ it "passes the monad class, this is ruby-fu?!" do
30
+ Identity.new(1)
31
+ .bind do |v|
32
+ expect(monad).to eq Identity
33
+ monad.new(v)
34
+ end
35
+ end
36
+
37
+ specify do
38
+ expect(
39
+ monad.new(1).bind { |value| monad.new(value + 1) }
40
+ ).to eq Identity.new(2)
41
+ end
42
+ end
43
+ specify { expect(Identity.new(Identity.new(1))).to eq Identity.new(1) }
44
+ end
45
+
@@ -0,0 +1,58 @@
1
+ require "spec_helper"
2
+
3
+ describe Null do
4
+ it "Null is a Singleton" do
5
+ expect(Null.instance).to be_a Null
6
+ expect { Null.new }.to raise_error(NoMethodError, "private method `new' called for Null:Class")
7
+ end
8
+
9
+ it "explicit conversions" do
10
+ expect(Null.to_s).to eq 'Null'
11
+ expect(Null.inspect).to eq 'Null'
12
+ end
13
+
14
+ it "answer if Null is null" do
15
+ expect(Null.null?).to eq true
16
+ end
17
+
18
+ it "answer if Null is some" do
19
+ expect(Null.some?).to eq false
20
+ end
21
+
22
+ it "compares to Null" do
23
+ # rubocop:disable Style/CaseEquality
24
+ expect(Null === Null.instance).to be_truthy
25
+ expect(Null.instance === Null).to be_truthy
26
+ expect(Null.instance).to eq Null
27
+ expect(Null).to eq Null.instance
28
+ expect(1).not_to be Null
29
+ expect(1).not_to be Null.instance
30
+ expect(Null.instance).not_to be 1
31
+ expect(Null).not_to be 1
32
+ expect(Null.instance).not_to be_nil
33
+ expect(Null).not_to be_nil
34
+ # rubocop:enable Style/CaseEquality
35
+ end
36
+
37
+ it "implicit conversions" do
38
+ null = Null.instance
39
+ expect(null.to_str).to eq ""
40
+ expect(null.to_ary).to eq []
41
+ expect("" + null).to eq ""
42
+
43
+ a, b, c = null
44
+ expect(a).to be_nil
45
+ expect(b).to be_nil
46
+ expect(c).to be_nil
47
+ end
48
+
49
+ it "mimicks other classes and returns Null for their public methods" do
50
+ class UnderMimickTest
51
+ def test; end
52
+ end
53
+
54
+ mimick = Null.mimic(UnderMimickTest)
55
+ expect(mimick.test).to be_null
56
+ expect { mimick.i_dont_exist }.to raise_error(NoMethodError)
57
+ end
58
+ end
@@ -0,0 +1,133 @@
1
+ require 'spec_helper'
2
+
3
+ # rubocop:disable Style/MixinUsage
4
+ include FunctionalLightService::Prelude::Option
5
+ # rubocop:enable Style/MixinUsage
6
+
7
+ describe FunctionalLightService::Option do
8
+ specify { expect(described_class::Some.new(0)).to be_a described_class::Some }
9
+ specify { expect(described_class::Some.new(0)).to be_a described_class }
10
+ specify { expect(described_class::Some.new(0)).to eq Some(0) }
11
+
12
+ specify { expect(described_class::None.new).to eq described_class::None.new }
13
+ specify { expect(described_class::None.new).to be_a described_class::None }
14
+ specify { expect(described_class::None.new).to be_a described_class }
15
+ specify { expect(described_class::None.new).to eq None }
16
+
17
+ it "join" do
18
+ expect(Some(Some(1))).to eq Some(1)
19
+ end
20
+
21
+ it "fmap" do
22
+ expect(Some(1).fmap { |n| n + 1 }).to eq Some(2)
23
+ expect(None.fmap { |n| n + 1 }).to eq None
24
+ end
25
+
26
+ it "map" do
27
+ expect(Some(1).map { |n| Some(n + 1) }).to eq Some(2)
28
+ expect(Some(1).map { |_n| None }).to eq None
29
+ expect(None.map { |n| Some(n + 1) }).to eq None
30
+ end
31
+
32
+ it "some?" do
33
+ expect(Some(1).some?).to be_truthy
34
+ expect(None.some?).to be_falsey
35
+ end
36
+
37
+ it "none?" do
38
+ expect(None.none?).to be_truthy
39
+ expect(Some(1).none?).to be_falsey
40
+ end
41
+
42
+ it "value" do
43
+ expect(Some(1).value).to eq 1
44
+ expect { None.value }.to raise_error NoMethodError
45
+ end
46
+
47
+ it "value_or" do
48
+ expect(Some(1).value_or(2)).to eq 1
49
+ expect(None.value_or(0)).to eq 0
50
+ end
51
+
52
+ it "+" do
53
+ expect(Some([1]) + None).to eq Some([1])
54
+ expect(Some(1) + None + None).to eq Some(1)
55
+ expect(Some(1) + Some(1)).to eq Some(2)
56
+ expect(None + Some(1)).to eq Some(1)
57
+ expect(None + None + Some(1)).to eq Some(1)
58
+ expect(None + None + Some(1) + None).to eq Some(1)
59
+ expect(None + Some(:foo => 1)).to eq Some(:foo => 1)
60
+ expect(Some([1]) + Some([1])).to eq Some([1, 1])
61
+ expect { Some([1]) + Some(1) }.to raise_error TypeError
62
+ end
63
+
64
+ it "inspect" do
65
+ expect(Some(1).inspect).to eq "Some(1)"
66
+ expect(described_class::None.new.inspect).to eq "None"
67
+ end
68
+
69
+ it "to_s" do
70
+ expect(Some(1).to_s).to eq "1"
71
+ expect(described_class::None.new.to_s).to eq ""
72
+ end
73
+
74
+ it "match" do
75
+ # expect(
76
+ # Some(0).match do
77
+ # Some(where { s == 1 }) { |_s| 99 }
78
+ # Some(where { s == 0 }) { |s| s + 1 }
79
+ # None() {}
80
+ # end
81
+ # ).to eq 1
82
+
83
+ expect(
84
+ Some(1).match do
85
+ None() { 0 }
86
+ Some() { |_s| 1 }
87
+ end
88
+ ).to eq 1
89
+
90
+ # expect(
91
+ # Some(1).match do
92
+ # None() { 0 }
93
+ # Some(where { s.is_a? Integer }) { |_s| 1 }
94
+ # end
95
+ # ).to eq 1
96
+
97
+ expect(
98
+ None.match do
99
+ None() { 0 }
100
+ Some() { 1 }
101
+ end
102
+ ).to eq 0
103
+ end
104
+
105
+ it "nil?" do
106
+ expect(described_class.some?(nil)).to eq None
107
+ expect(described_class.some?(1)).to be_some
108
+ expect(described_class.some?(1)).to eq Some(1)
109
+ end
110
+
111
+ it "any?" do
112
+ expect(described_class.any?(nil)).to be_none
113
+ expect(described_class.any?(None)).to be_none
114
+ expect(described_class.any?("")).to be_none
115
+ expect(described_class.any?([])).to be_none
116
+ expect(described_class.any?({})).to be_none
117
+ expect(described_class.any?([1])).to eq Some([1])
118
+ expect(described_class.any?(:foo => 1)).to eq Some(:foo => 1)
119
+ expect(described_class.any?(1)).to eq Some(1)
120
+ end
121
+
122
+ it "try!" do
123
+ expect(described_class.try! { raise "error" }).to be_none
124
+ end
125
+ end
126
+
127
+ require_relative 'monad_axioms'
128
+
129
+ describe FunctionalLightService::Option::Some do
130
+ it_behaves_like 'a Monad' do
131
+ let(:monad) { described_class }
132
+ end
133
+ end
@@ -0,0 +1,65 @@
1
+ require 'spec_helper'
2
+ require_relative '../monad_axioms'
3
+ require_relative 'result_shared'
4
+
5
+ describe FunctionalLightService::Result::Failure do
6
+ include FunctionalLightService::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(FunctionalLightService::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(FunctionalLightService::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(FunctionalLightService::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(FunctionalLightService::Monad::NotMonadError)
64
+ end
65
+ end
@@ -0,0 +1,154 @@
1
+ require 'spec_helper'
2
+
3
+ describe FunctionalLightService::Result do
4
+ include FunctionalLightService::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 FunctionalLightService::Prelude::Result
13
+ alias :m :method
14
+
15
+ def call
16
+ init >>
17
+ m(:validate) >>
18
+ m(:send) >>
19
+ m(:parse)
20
+ end
21
+
22
+ def init
23
+ Success(:step => 1)
24
+ end
25
+
26
+ def validate(i)
27
+ i[:step] = i[:step] + 1
28
+ Success(i)
29
+ end
30
+
31
+ def send(i)
32
+ i[:step] = i[:step] + 1
33
+ Failure("Error @ #{i.fetch(:step)}")
34
+ end
35
+
36
+ def parse(i)
37
+ i[:step] = i[:step] + 1
38
+ Success(i)
39
+ end
40
+ end
41
+
42
+ test = ChainUnderTest.new
43
+
44
+ expect(test.call).to eq Failure("Error @ 3")
45
+ end
46
+
47
+ it "expects an Result" do
48
+ def returns_non_result(_i)
49
+ 2
50
+ end
51
+
52
+ expect { Success(1) >> method(:returns_non_result) }.to \
53
+ raise_error(FunctionalLightService::Monad::NotMonadError)
54
+ end
55
+
56
+ it "works with a block" do
57
+ expect(
58
+ Success(1).map { |i| Success(i + 1) }
59
+ ).to eq Success(2)
60
+ end
61
+
62
+ it "works with a lambda" do
63
+ expect(
64
+ Success(1) >> ->(i) { Success(i + 1) }
65
+ ).to eq Success(2)
66
+ end
67
+
68
+ it "does not catch exceptions" do
69
+ expect do
70
+ Success(1) >> ->(_i) { raise "error" }
71
+ end.to raise_error(RuntimeError)
72
+ end
73
+ end
74
+
75
+ context "using self as the context for success" do
76
+ class SelfContextUnderTest
77
+ include FunctionalLightService::Prelude::Result
78
+
79
+ def call
80
+ @step = 0
81
+ Success(self)
82
+ .map(&:validate)
83
+ .map(&:build)
84
+ .map(&:send)
85
+ end
86
+
87
+ def validate
88
+ @step = 1
89
+ Success(self)
90
+ end
91
+
92
+ def build
93
+ @step = 2
94
+ Success(self)
95
+ end
96
+
97
+ def send
98
+ @step = 3
99
+ Success(self)
100
+ end
101
+
102
+ def inspect
103
+ "Step #{@step}"
104
+ end
105
+
106
+ # # def self.procify(*meths)
107
+ # # meths.each do |m|
108
+ # # new_m = "__#{m}__procified".to_sym
109
+ # # alias new_m m
110
+ # # define_method new_m do |ctx|
111
+ # # method(m)
112
+ # # end
113
+ # # end
114
+ # # end
115
+
116
+ # procify :send
117
+ end
118
+
119
+ it "works" do
120
+ test = SelfContextUnderTest.new.call
121
+ expect(test).to be_a described_class::Success
122
+ expect(test.inspect).to eq "Success(Step 3)"
123
+ end
124
+ end
125
+
126
+ context "<< (pipe)" do
127
+ it "ignores the output of pipe" do
128
+ acc = "ctx: "
129
+ log = ->(ctx) { acc += ctx.inspect }
130
+
131
+ actual = Success(1).pipe(log).map { Success(2) }
132
+ expect(actual).to eq Success(2)
133
+ expect(acc).to eq "ctx: Success(1)"
134
+ end
135
+
136
+ it "works with <<" do
137
+ log = ->(n) { n.value + 1 }
138
+ foo = ->(n) { Success(n + 1) }
139
+
140
+ Success(1) << log >> foo
141
+ end
142
+ end
143
+
144
+ context ">= (try)" do
145
+ it "try (>=) catches errors and wraps them as Failure" do
146
+ def error(ctx)
147
+ raise "error #{ctx}"
148
+ end
149
+
150
+ actual = Success(1) >= method(:error)
151
+ expect(actual.inspect).to eq "Failure(#<RuntimeError: error 1>)"
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,24 @@
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
+ expect(result.new(:a => 1).to_s).to eq "{:a=>1}"
17
+ end
18
+
19
+ it "#inspect" do
20
+ expect(result.new(1).inspect).to eq "#{result_name}(1)"
21
+ expect(result.new(:a => 1).inspect).to eq "#{result_name}({:a=>1})"
22
+ end
23
+ end
24
+
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+ require_relative '../monad_axioms'
3
+ require_relative 'result_shared'
4
+
5
+ describe FunctionalLightService::Result::Success do
6
+ include FunctionalLightService::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