functional-light-service 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
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