to-result 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bc8202a00d4cc8938cac602f09b0a9b0694a29421bedb0c71be82f94fad66e89
4
- data.tar.gz: 783334b0487c880174639c699eb61fc6eacd695086a5c5b9b531947bd1645b30
3
+ metadata.gz: d9f9dde009ad8e058a1b645888a53f768c8d2571cd91266f77b1017c8aa469ff
4
+ data.tar.gz: fcbd389f7a3f8d0ddf101cb1064a70a225c039f3cc41c19ca64cac3229469fb9
5
5
  SHA512:
6
- metadata.gz: 4e2b40217e547f2cabbcc84847f1f257676863997c088f4e2c43416a608fd804b07bf7586e95d6ab60b8c70fa501b6cd297deb7d9ad7a86f0acade0977ef5f64
7
- data.tar.gz: feceedd4425f782ee47637da08a69fbef6f54b226a5bd104e6a8f22ea219a9b4be1fd30ffac56e88df0ec1aa2212f4c2ea2994b81f51155f8d1dae8ff29fb41b
6
+ metadata.gz: '08e53f93d442d8d5570cb03eac07df8865f6cb4c14bf1daf0eb9f0db064553d879473379cffdc2cd3707100a690d2aebd26340b69d8faea087c9eb17251e5c71'
7
+ data.tar.gz: 435173009f18e28de610c30424070814b4550e4bb13f5359fda42130eb5fc32887c6004cc6c6aa02b7799b29d74f5b3824b782fdf9c235d90ef5fbd4559dbd07
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.1](https://github.com/a-chris/to-result/tree/0.1.1) (2023-08-16)
4
+
5
+ HOTFIXES:
6
+
7
+ - Always pass the error to the `on_error` callable object instead of mixed `error/Failure(error)`
8
+
3
9
  ## [0.1.0](https://github.com/a-chris/to-result/tree/0.1.0) (2022-10-28)
4
10
 
5
11
  - Explicit requires `dry-monads` >= 1.x, just for convenience
data/Gemfile CHANGED
@@ -5,10 +5,8 @@ source 'https://rubygems.org'
5
5
  # Specify your gem's dependencies in pulsarcli.gemspec
6
6
  gemspec
7
7
 
8
- gem 'dry-monads', '~> 1.5'
8
+ gem 'dry-monads', '~> 1.6'
9
9
 
10
10
  gem 'byebug', '~> 11.1'
11
11
 
12
- gem 'minitest', '~> 5.16'
13
-
14
- gem 'mocha', '~> 1.15'
12
+ gem 'rspec'
data/Gemfile.lock CHANGED
@@ -1,33 +1,44 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- to-result (0.1.0)
4
+ to-result (0.2.0)
5
5
  dry-monads (~> 1.5)
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
10
  byebug (11.1.3)
11
- concurrent-ruby (1.1.10)
12
- dry-core (0.9.1)
11
+ concurrent-ruby (1.2.2)
12
+ diff-lcs (1.5.0)
13
+ dry-core (1.0.1)
13
14
  concurrent-ruby (~> 1.0)
14
15
  zeitwerk (~> 2.6)
15
- dry-monads (1.5.0)
16
+ dry-monads (1.6.0)
16
17
  concurrent-ruby (~> 1.0)
17
- dry-core (~> 0.9, >= 0.9)
18
+ dry-core (~> 1.0, < 2)
18
19
  zeitwerk (~> 2.6)
19
- minitest (5.16.3)
20
- mocha (1.15.0)
21
- zeitwerk (2.6.1)
20
+ rspec (3.12.0)
21
+ rspec-core (~> 3.12.0)
22
+ rspec-expectations (~> 3.12.0)
23
+ rspec-mocks (~> 3.12.0)
24
+ rspec-core (3.12.2)
25
+ rspec-support (~> 3.12.0)
26
+ rspec-expectations (3.12.3)
27
+ diff-lcs (>= 1.2.0, < 2.0)
28
+ rspec-support (~> 3.12.0)
29
+ rspec-mocks (3.12.6)
30
+ diff-lcs (>= 1.2.0, < 2.0)
31
+ rspec-support (~> 3.12.0)
32
+ rspec-support (3.12.1)
33
+ zeitwerk (2.6.11)
22
34
 
23
35
  PLATFORMS
24
36
  ruby
25
37
 
26
38
  DEPENDENCIES
27
39
  byebug (~> 11.1)
28
- dry-monads (~> 1.5)
29
- minitest (~> 5.16)
30
- mocha (~> 1.15)
40
+ dry-monads (~> 1.6)
41
+ rspec
31
42
  to-result!
32
43
 
33
44
  BUNDLED WITH
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
 
2
2
  # ToResult
3
3
 
4
- ToResult is a wrapper built over `dry-monads` to make the `Do Notation`, `Result` and `Try` concepts more handy and consistent to use, in particular to implement the **Railway Pattern**.
4
+ ToResult is a wrapper built over `dry-monads` to make the `Do Notation`, `Result` and `Try` concepts more handy and consistent to use, especially to implement the **Railway Pattern**.
5
5
 
6
6
  ## Why I created ToResult
7
7
 
@@ -120,7 +120,7 @@ class MyClass
120
120
  end
121
121
  ```
122
122
 
123
- now you can always use `ToResult` all the time you wanted to use `Success`, `Failure` or `Try` but with a more convenient interface and consistent behaviour.
123
+ now you can always use `ToResult` all the time you wanted to use `Try` or return `Success/Failure` but with a more convenient interface and consistent behaviour, my goal is to have a solution that can be used for every use-case.
124
124
 
125
125
  Look at this:
126
126
 
@@ -144,6 +144,30 @@ ToResult(only: [ArgumentError]) { yield Failure(YourCustomError.new('error code'
144
144
  # raises YourCustomError('error code')
145
145
  ```
146
146
 
147
+ ## Local and global callback on errors
148
+ to-result gives you the possibility to define a callback to be called when an error is raised inside the `ToResult` block, this is a handy place to log errors.
149
+
150
+ You can define a global callback, usually defined into an initializer:
151
+
152
+ ```ruby
153
+ # initializers/to_result.rb
154
+
155
+ ToResultMixin.configure do |c|
156
+ c.on_error = Proc.new { |e| Logger.log_error(e) }
157
+ end
158
+ ```
159
+
160
+ or a local callback:
161
+
162
+ ```ruby
163
+ ToResult(on_error: proc { |e| Logger.log_error(e) }) do
164
+ yield Failure(StandardError.new('error code'))
165
+ end
166
+ ```
167
+
168
+ you can even use both at the same time but keep in mind that **local callback overrides the global one**.
169
+
170
+
147
171
  ## Changelog
148
172
 
149
173
  [Changelog](CHANGELOG.md)
@@ -154,7 +178,14 @@ I'm already planning to implement some useful features:
154
178
  - [x] configurable error logging when an exception is catched inside `DoResult`
155
179
  e.g. sending the log to Airbrake or whathever service you are using
156
180
  - [x] transform/process the catched error => this can be handled with `alt_map` or other methods already available in `dry-monads`
157
- - [ ] any other suggestion would be appreciated 😁
181
+ - [ ] any type of suggestion is appreciated 😁
182
+
183
+ ## Relasing a new version
184
+
185
+ ```
186
+ gem build to-result
187
+ gem push to-result-<version>.gem
188
+ ```
158
189
 
159
190
  ## Authors
160
191
 
data/lib/to-result.rb CHANGED
@@ -7,7 +7,7 @@ module ToResultMixin
7
7
  attr_accessor :on_error
8
8
 
9
9
  def on_error=(value)
10
- raise TypeError.new('on_error is expected to be a callable object') unless value.respond_to?(:call)
10
+ raise TypeError, 'on_error is expected to be a callable object' unless value.respond_to?(:call)
11
11
 
12
12
  @on_error = value
13
13
  end
@@ -39,23 +39,24 @@ module ToResultMixin
39
39
  # @return [Success]
40
40
  # @return [Failure]
41
41
  #
42
- def ToResult(only: [StandardError], **args, &f)
42
+ def ToResult(only: [StandardError], **args, &block)
43
43
  # on_error included in args so we can distinguish when it's passed but it's nil
44
44
  # from when it's not passed at all
45
45
  on_error ||= args.key?(:on_error) ? args[:on_error] : @@configuration.on_error
46
46
 
47
- f_wrapper =
48
- Proc.new do
49
- f.call
47
+ block_wrapper =
48
+ proc do
49
+ block.call
50
50
  rescue Dry::Monads::Do::Halt => e
51
- error = e.result
51
+ failure = error = e.result
52
+ error = error.failure if error.respond_to?(:failure)
52
53
  on_error.call(error) if on_error.respond_to?(:call)
53
- return error
54
+ return failure
54
55
  rescue *only => e
55
56
  on_error.call(e) if on_error.respond_to?(:call)
56
57
  raise e
57
58
  end
58
59
 
59
- Try.run(only, f_wrapper).to_result
60
+ Try.run(only, block_wrapper).to_result
60
61
  end
61
62
  end
@@ -0,0 +1,15 @@
1
+ class FakeObject
2
+ extend ToResultMixin
3
+
4
+ def self.log_error(e)
5
+ e
6
+ end
7
+
8
+ def self.log_error_copy(e)
9
+ e
10
+ end
11
+
12
+ def self.raise_do_halt_error(failure, on_error: nil)
13
+ ToResult(on_error: on_error) { yield failure }
14
+ end
15
+ end
@@ -0,0 +1,116 @@
1
+ require 'byebug'
2
+
3
+ require './lib/to-result'
4
+ require './spec/support/fake_object'
5
+
6
+
7
+ RSpec.describe ToResultMixin do
8
+ include ToResultMixin
9
+
10
+ let(:value) { 'hello world!' }
11
+
12
+ before do
13
+ # reset the configuration before each test
14
+ ToResultMixin.configure { |c| c = {} }
15
+ end
16
+
17
+ describe 'result handling' do
18
+ it 'returns a Success with the value of the block' do
19
+ expect(ToResult { value }).to eq(Success(value))
20
+ end
21
+
22
+ it 'returns a Success without the Success of the block' do
23
+ expected = Success(value)
24
+ expect(ToResult { expected }).to eq(Success(expected))
25
+ end
26
+
27
+ it 'returns a Failure with the exception raised by the block' do
28
+ expected = StandardError.new(value)
29
+ expect(ToResult { raise expected }).to eq(Failure(expected))
30
+ end
31
+
32
+ it 'returns a Failure if the error class is included in `only`' do
33
+ expected = ArgumentError.new(value)
34
+ expect(ToResult(only: [ArgumentError]) { raise expected }).to eq(Failure(expected))
35
+ end
36
+
37
+ it 'raises an error if the error class is not included in `only`' do
38
+ expected = NameError.new(value)
39
+ expect { ToResult(only: [ArgumentError]) { raise expected } }.to raise_error(NameError)
40
+ end
41
+
42
+ it 'returns Failure if the block raises a Dry::Monads::Do::Halt error' do
43
+ expected = Dry::Monads::Failure(StandardError.new(value))
44
+ # it equals to ToResult { yield expected }
45
+ expect(FakeObject.raise_do_halt_error(expected)).to eq(expected)
46
+ end
47
+ end
48
+
49
+ describe 'on_error' do
50
+ it 'passes the unwrapped error to on_error' do
51
+ local_on_error = proc { |e| FakeObject.log_error(e) }
52
+
53
+ expected = StandardError.new(value)
54
+ failure = Failure(expected)
55
+ expect(FakeObject).to receive(:log_error).and_return(expected).once
56
+ # it equals to ToResult { yield expected }
57
+ expect(FakeObject.raise_do_halt_error(failure, on_error: local_on_error)).to eq(Dry::Monads::Failure.new(expected))
58
+ end
59
+
60
+ context 'when gloval callback is not defiend' do
61
+ it 'uses the local on_error' do
62
+ local_on_error = proc { |e| FakeObject.log_error(e) }
63
+
64
+ expected = StandardError.new(value)
65
+ expect(FakeObject).to receive(:log_error).and_return(expected).once
66
+ expect(ToResult(on_error: local_on_error) { raise expected }).to eq(Failure(expected))
67
+ end
68
+
69
+ it 'does not raise error if local on_error is not a proc' do
70
+ local_on_error = 'not a proc'
71
+
72
+ expected = StandardError.new(value)
73
+ FakeObject.methods(false).each { |m| expect(FakeObject).to receive(m).never }
74
+ expect(ToResult(on_error: local_on_error) { raise expected }).to eq(Failure(expected))
75
+ end
76
+ end
77
+
78
+ context 'when global callback is invalid' do
79
+ it 'raises an error' do
80
+ expect do
81
+ ToResultMixin.configure { |c| c.on_error = 'invalid value, should be a proc' }
82
+ end.to raise_error(TypeError)
83
+ end
84
+ end
85
+
86
+ context 'when global callback if valid and defined globally' do
87
+ before do
88
+ ToResultMixin.configure do |c|
89
+ c.on_error = proc { |e| FakeObject.log_error(e) }
90
+ end
91
+ end
92
+
93
+ it 'calls the global callback' do
94
+ expected = StandardError.new(value)
95
+ expect(FakeObject).to receive(:log_error).with(expected).once
96
+ expect(ToResult { raise expected }).to eq(Failure(expected))
97
+ end
98
+
99
+ it 'uses local on_error over the global one' do
100
+ local_on_error = Proc.new { |e| FakeObject.log_error_copy(e) }
101
+
102
+ expected = StandardError.new(value)
103
+ expect(FakeObject).to receive(:log_error_copy).with(expected).once
104
+ expect(ToResult(on_error: local_on_error) { raise expected }).to eq(Failure(expected))
105
+ end
106
+
107
+ it 'uses local on_error over the global one even if nil' do
108
+ local_on_error = nil
109
+
110
+ expected = StandardError.new(value)
111
+ FakeObject.methods(false).each { |m| expect(FakeObject).to receive(m).never }
112
+ expect(ToResult(on_error: local_on_error) { raise expected }).to eq(Failure(expected))
113
+ end
114
+ end
115
+ end
116
+ end
data/to-result.gemspec CHANGED
@@ -5,7 +5,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = 'to-result'
8
- s.version = '0.1.0'
8
+ s.version = '0.2.0'
9
9
  s.summary = 'A wrapper over dry-monads to offer a handy and consistent way to implement the Railway pattern.'
10
10
  s.description = 'A wrapper over dry-monads to offer a handy and consistent way to implement the Railway pattern.'
11
11
  s.authors = ['Christian Toscano']
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: to-result
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Christian Toscano
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-10-27 00:00:00.000000000 Z
11
+ date: 2023-08-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dry-monads
@@ -38,8 +38,8 @@ files:
38
38
  - README.md
39
39
  - bin/console
40
40
  - lib/to-result.rb
41
- - tests/support/fake_logger.rb
42
- - tests/to_result_test.rb
41
+ - spec/support/fake_object.rb
42
+ - spec/to_result_spec.rb
43
43
  - to-result.gemspec
44
44
  homepage: https://github.com/a-chris/to-result
45
45
  licenses:
@@ -60,7 +60,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
60
60
  - !ruby/object:Gem::Version
61
61
  version: '0'
62
62
  requirements: []
63
- rubygems_version: 3.2.22
63
+ rubygems_version: 3.4.10
64
64
  signing_key:
65
65
  specification_version: 4
66
66
  summary: A wrapper over dry-monads to offer a handy and consistent way to implement
@@ -1,9 +0,0 @@
1
- class FakeLogger
2
- def self.log_error
3
- true
4
- end
5
-
6
- def self.return_error(e)
7
- e
8
- end
9
- end
@@ -1,113 +0,0 @@
1
- require 'minitest/autorun'
2
- require 'mocha/minitest'
3
- require 'byebug'
4
-
5
- require './lib/to-result'
6
- require './tests/support/fake_logger'
7
-
8
- class ToResultTest < Minitest::Test
9
- include ToResultMixin
10
-
11
- def setup
12
- super
13
- @value = 'hello world!'
14
- end
15
-
16
- def teardown
17
- super
18
-
19
- # reset the configuration after each test
20
- ToResultMixin.configure { |c| c = {} }
21
- end
22
-
23
- def test_string
24
- assert ToResult { @value } == Success(@value)
25
- end
26
-
27
- def test_success
28
- expected = Success(@value)
29
- assert ToResult { expected } == Success(expected)
30
- end
31
-
32
- def test_exception
33
- expected = StandardError.new(@value)
34
- assert ToResult { raise expected } == Failure(expected)
35
- end
36
-
37
- def test_exception_included_in_exceptions_list
38
- expected = ArgumentError.new(@value)
39
- assert ToResult(only: [ArgumentError]) { raise expected } == Failure(expected)
40
- end
41
-
42
- def test_exception_not_included_in_exceptions_list
43
- expected = NameError.new(@value)
44
- assert_raises(NameError) { ToResult(only: [ArgumentError]) { raise expected } }
45
- end
46
-
47
- def test_yield_failure
48
- expected = Failure(@value)
49
- # this will raise a Dry::Monads::Do::Halt exception
50
- assert ToResult { yield expected } == expected
51
- end
52
-
53
- def test_yield_failure_exception
54
- expected = Failure(StandardError.new(@value))
55
- # this will raise a Dry::Monads::Do::Halt exception
56
- assert ToResult { yield expected } == expected
57
- end
58
-
59
- def test_invalid_global_on_error
60
- assert_raises(TypeError) do
61
- ToResultMixin.configure { |c| c.on_error = 'invalid value' }
62
- end
63
- end
64
-
65
- def setup_global_on_error
66
- # creating a clean room just for testing purpose
67
- clean_room = Class.new(Object)
68
- clean_room.new.instance_eval do
69
- ToResultMixin.configure do |c|
70
- c.on_error = Proc.new { FakeLogger.log_error }
71
- end
72
- end
73
- end
74
-
75
- def test_global_on_error
76
- setup_global_on_error
77
-
78
- FakeLogger.expects(:log_error).once
79
-
80
- expected = StandardError.new(@value)
81
- assert ToResult { raise expected } == Failure(expected)
82
- end
83
-
84
- def test_local_on_error_overrides_global
85
- setup_global_on_error
86
-
87
- FakeLogger.expects(:log_error).once
88
-
89
- local_on_error = Proc.new { FakeLogger.log_error }
90
-
91
- expected = StandardError.new(@value)
92
- assert ToResult(on_error: local_on_error) { raise expected } == Failure(expected)
93
- end
94
-
95
- def test_local_on_error_overrides_global_nil
96
- setup_global_on_error
97
-
98
- FakeLogger.expects(:log_error).never
99
-
100
- local_on_error = nil
101
-
102
- expected = StandardError.new(@value)
103
- assert ToResult(on_error: local_on_error) { raise expected } == Failure(expected)
104
- end
105
-
106
- def test_local_on_error_without_global
107
- local_on_error = Proc.new { |e| FakeLogger.return_error(e) }
108
-
109
- expected = StandardError.new(@value)
110
- FakeLogger.expects(:return_error).with(expected).returns(expected).once
111
- assert ToResult(on_error: local_on_error) { raise expected } == Failure(expected)
112
- end
113
- end