to-result 0.0.4 → 0.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +16 -3
- data/Gemfile +1 -1
- data/Gemfile.lock +9 -5
- data/README.md +37 -10
- data/lib/to-result.rb +26 -10
- data/tests/support/fake_logger.rb +4 -0
- data/tests/to_result_test.rb +44 -4
- data/to-result.gemspec +3 -1
- metadata +17 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bc8202a00d4cc8938cac602f09b0a9b0694a29421bedb0c71be82f94fad66e89
|
4
|
+
data.tar.gz: 783334b0487c880174639c699eb61fc6eacd695086a5c5b9b531947bd1645b30
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4e2b40217e547f2cabbcc84847f1f257676863997c088f4e2c43416a608fd804b07bf7586e95d6ab60b8c70fa501b6cd297deb7d9ad7a86f0acade0977ef5f64
|
7
|
+
data.tar.gz: feceedd4425f782ee47637da08a69fbef6f54b226a5bd104e6a8f22ea219a9b4be1fd30ffac56e88df0ec1aa2212f4c2ea2994b81f51155f8d1dae8ff29fb41b
|
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,19 @@
|
|
1
1
|
# Changelog
|
2
|
+
|
3
|
+
## [0.1.0](https://github.com/a-chris/to-result/tree/0.1.0) (2022-10-28)
|
4
|
+
|
5
|
+
- Explicit requires `dry-monads` >= 1.x, just for convenience
|
6
|
+
|
7
|
+
BREAKING CHANGES:
|
8
|
+
|
9
|
+
- The arrays of exceptions to catch is now received using the `only` argument key, e.g. `ToResult(only: [ArgumentError])`
|
10
|
+
- Setting the global `on_error` with a non-callable object (an object that does not respond to `call`) will raise `TypeError`
|
11
|
+
|
12
|
+
NEW FEATURES:
|
13
|
+
|
14
|
+
- Added local `on_error` that can be used to override or run a one-time block when an error is catched
|
15
|
+
`ToResult(on_error: Proc { |e| puts e })`
|
16
|
+
|
2
17
|
## [0.0.4](https://github.com/a-chris/to-result/tree/0.0.4) (2022-10-08)
|
3
18
|
|
4
19
|
- Adds a global `configuration` object for ToResult
|
@@ -20,6 +35,4 @@
|
|
20
35
|
|
21
36
|
[Full Changelog](https://github.com/a-chris/to-result/compare/8dce552d6d07a2a145c45dbf7d05dbe6b0c5c578...0.0.1)
|
22
37
|
|
23
|
-
|
24
|
-
|
25
|
-
\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*
|
38
|
+
\* _This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)_
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,27 +1,31 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
to-result (0.0
|
4
|
+
to-result (0.1.0)
|
5
|
+
dry-monads (~> 1.5)
|
5
6
|
|
6
7
|
GEM
|
7
8
|
remote: https://rubygems.org/
|
8
9
|
specs:
|
9
10
|
byebug (11.1.3)
|
10
11
|
concurrent-ruby (1.1.10)
|
11
|
-
dry-core (0.
|
12
|
+
dry-core (0.9.1)
|
12
13
|
concurrent-ruby (~> 1.0)
|
13
|
-
|
14
|
+
zeitwerk (~> 2.6)
|
15
|
+
dry-monads (1.5.0)
|
14
16
|
concurrent-ruby (~> 1.0)
|
15
|
-
dry-core (~> 0.
|
17
|
+
dry-core (~> 0.9, >= 0.9)
|
18
|
+
zeitwerk (~> 2.6)
|
16
19
|
minitest (5.16.3)
|
17
20
|
mocha (1.15.0)
|
21
|
+
zeitwerk (2.6.1)
|
18
22
|
|
19
23
|
PLATFORMS
|
20
24
|
ruby
|
21
25
|
|
22
26
|
DEPENDENCIES
|
23
27
|
byebug (~> 11.1)
|
24
|
-
dry-monads (~> 1.
|
28
|
+
dry-monads (~> 1.5)
|
25
29
|
minitest (~> 5.16)
|
26
30
|
mocha (~> 1.15)
|
27
31
|
to-result!
|
data/README.md
CHANGED
@@ -5,7 +5,7 @@ ToResult is a wrapper built over `dry-monads` to make the `Do Notation`, `Result
|
|
5
5
|
|
6
6
|
## Why I created ToResult
|
7
7
|
|
8
|
-
`dry-monads`
|
8
|
+
`dry-monads` requires to write boilerplate code everytime we want a method to return a `Success` or `Failure`, for example:
|
9
9
|
|
10
10
|
```ruby
|
11
11
|
def my_method
|
@@ -15,7 +15,7 @@ rescue StandardError => e
|
|
15
15
|
end
|
16
16
|
```
|
17
17
|
|
18
|
-
|
18
|
+
yes, we can use `Try` which makes the code easier to read and faster to write:
|
19
19
|
```ruby
|
20
20
|
def my_method
|
21
21
|
Try do
|
@@ -24,11 +24,11 @@ def my_method
|
|
24
24
|
end
|
25
25
|
```
|
26
26
|
|
27
|
-
but I feel like `to_result` is not really visible at the end of the
|
27
|
+
but I feel like `to_result` is not really visible at the end of the block and if you forget to write it (as always happens to me) your application blows up.
|
28
28
|
|
29
|
-
But this is not the
|
29
|
+
But this is not the biggest problem, bear with me.
|
30
30
|
|
31
|
-
One of the biggest problem is that we cannot use the
|
31
|
+
One of the biggest problem is that we cannot use the **Do Notation** inside a `Try` block:
|
32
32
|
```ruby
|
33
33
|
# this will return a Failure(Dry::Monads::Do::Halt)
|
34
34
|
def my_method
|
@@ -50,7 +50,30 @@ rescue StandardError => e
|
|
50
50
|
end
|
51
51
|
```
|
52
52
|
|
53
|
-
because they will raise a `Dry::Monads::Do::Halt` exception and the original
|
53
|
+
because they will raise a `Dry::Monads::Do::Halt` exception and the original error will be forever lost if we do not "unbox" the exception with `e.result` like this:
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
def my_method
|
57
|
+
yield Failure('error code')
|
58
|
+
rescue Dry::Monads::Do::Halt => e
|
59
|
+
return e.result
|
60
|
+
rescue StandardError => e
|
61
|
+
Failure(e)
|
62
|
+
end
|
63
|
+
```
|
64
|
+
|
65
|
+
to be honest this is an implementation detail I don't want to care about while I'm writing my business logic and as far as I've seen this is really hard for junior developers to figure out what is happening with `Do::Halt`.
|
66
|
+
|
67
|
+
With this gem, `to-result`, that piece of code can be written as:
|
68
|
+
```ruby
|
69
|
+
def my_method
|
70
|
+
ToResult do
|
71
|
+
yield Failure('error code')
|
72
|
+
end
|
73
|
+
end
|
74
|
+
```
|
75
|
+
|
76
|
+
and it will return `Failure('error code')` without all the effort to think about `Do::Halt`. Moreover, you can keep using `ToResult` everytime you could have used `Try` or monads in general, so you have just **one way** to write monads in your code.
|
54
77
|
|
55
78
|
## Installation
|
56
79
|
|
@@ -114,19 +137,23 @@ ToResult { yield Failure('error code') }
|
|
114
137
|
ToResult { yield Failure(StandardError.new('error code')) }
|
115
138
|
# returns Failure(StandardError('error code'))
|
116
139
|
|
117
|
-
ToResult([YourCustomError]) { yield Failure(YourCustomError.new('error code')) }
|
140
|
+
ToResult(only: [YourCustomError]) { yield Failure(YourCustomError.new('error code')) }
|
118
141
|
# returns Failure(YourCustomError('error code'))
|
119
142
|
|
120
|
-
ToResult([ArgumentError]) { yield Failure(YourCustomError.new('error code')) }
|
143
|
+
ToResult(only: [ArgumentError]) { yield Failure(YourCustomError.new('error code')) }
|
121
144
|
# raises YourCustomError('error code')
|
122
145
|
```
|
123
146
|
|
147
|
+
## Changelog
|
148
|
+
|
149
|
+
[Changelog](CHANGELOG.md)
|
150
|
+
|
124
151
|
## Roadmap
|
125
152
|
I'm already planning to implement some useful features:
|
126
153
|
- [x] write more examples/documentation/tests
|
127
|
-
- [
|
154
|
+
- [x] configurable error logging when an exception is catched inside `DoResult`
|
128
155
|
e.g. sending the log to Airbrake or whathever service you are using
|
129
|
-
- [
|
156
|
+
- [x] transform/process the catched error => this can be handled with `alt_map` or other methods already available in `dry-monads`
|
130
157
|
- [ ] any other suggestion would be appreciated 😁
|
131
158
|
|
132
159
|
## Authors
|
data/lib/to-result.rb
CHANGED
@@ -5,6 +5,12 @@ module ToResultMixin
|
|
5
5
|
|
6
6
|
class Configuration
|
7
7
|
attr_accessor :on_error
|
8
|
+
|
9
|
+
def on_error=(value)
|
10
|
+
raise TypeError.new('on_error is expected to be a callable object') unless value.respond_to?(:call)
|
11
|
+
|
12
|
+
@on_error = value
|
13
|
+
end
|
8
14
|
end
|
9
15
|
|
10
16
|
@@configuration = Configuration.new
|
@@ -19,27 +25,37 @@ module ToResultMixin
|
|
19
25
|
#
|
20
26
|
# ToResult executes a block of code and returns Success or Failure.
|
21
27
|
# All exceptions inherited from StandardError are catched and
|
22
|
-
# converted to Failure or you can pass a list of exceptions
|
28
|
+
# converted to Failure or you can pass a custom list of exceptions
|
29
|
+
# to catch using `only`.
|
30
|
+
#
|
31
|
+
# Passing the `on_error` block overrides the global on_error defined in to the Configuration,
|
32
|
+
# it is possible to pass `nil` to not execute a block when an error is catched.
|
23
33
|
#
|
24
|
-
#
|
25
|
-
# @param [
|
34
|
+
#
|
35
|
+
# @param [Array<Class>] only is the array of Exception to catch
|
36
|
+
# @param [Proc|Method] on_error is the function to be executed in case of error. It overrides the global on_error.
|
37
|
+
# @param [Proc] &f is the block to run
|
26
38
|
#
|
27
39
|
# @return [Success]
|
28
40
|
# @return [Failure]
|
29
41
|
#
|
30
|
-
def ToResult(
|
31
|
-
|
32
|
-
|
42
|
+
def ToResult(only: [StandardError], **args, &f)
|
43
|
+
# on_error included in args so we can distinguish when it's passed but it's nil
|
44
|
+
# from when it's not passed at all
|
45
|
+
on_error ||= args.key?(:on_error) ? args[:on_error] : @@configuration.on_error
|
46
|
+
|
47
|
+
f_wrapper =
|
33
48
|
Proc.new do
|
34
49
|
f.call
|
35
50
|
rescue Dry::Monads::Do::Halt => e
|
36
51
|
error = e.result
|
37
|
-
|
52
|
+
on_error.call(error) if on_error.respond_to?(:call)
|
38
53
|
return error
|
39
|
-
rescue *
|
40
|
-
|
54
|
+
rescue *only => e
|
55
|
+
on_error.call(e) if on_error.respond_to?(:call)
|
41
56
|
raise e
|
42
57
|
end
|
43
|
-
|
58
|
+
|
59
|
+
Try.run(only, f_wrapper).to_result
|
44
60
|
end
|
45
61
|
end
|
data/tests/to_result_test.rb
CHANGED
@@ -36,12 +36,12 @@ class ToResultTest < Minitest::Test
|
|
36
36
|
|
37
37
|
def test_exception_included_in_exceptions_list
|
38
38
|
expected = ArgumentError.new(@value)
|
39
|
-
assert ToResult([ArgumentError]) { raise expected } == Failure(expected)
|
39
|
+
assert ToResult(only: [ArgumentError]) { raise expected } == Failure(expected)
|
40
40
|
end
|
41
41
|
|
42
42
|
def test_exception_not_included_in_exceptions_list
|
43
43
|
expected = NameError.new(@value)
|
44
|
-
assert_raises(NameError) { ToResult([ArgumentError]) { raise expected } }
|
44
|
+
assert_raises(NameError) { ToResult(only: [ArgumentError]) { raise expected } }
|
45
45
|
end
|
46
46
|
|
47
47
|
def test_yield_failure
|
@@ -56,9 +56,13 @@ class ToResultTest < Minitest::Test
|
|
56
56
|
assert ToResult { yield expected } == expected
|
57
57
|
end
|
58
58
|
|
59
|
-
def
|
60
|
-
|
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
|
61
64
|
|
65
|
+
def setup_global_on_error
|
62
66
|
# creating a clean room just for testing purpose
|
63
67
|
clean_room = Class.new(Object)
|
64
68
|
clean_room.new.instance_eval do
|
@@ -66,8 +70,44 @@ class ToResultTest < Minitest::Test
|
|
66
70
|
c.on_error = Proc.new { FakeLogger.log_error }
|
67
71
|
end
|
68
72
|
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_global_on_error
|
76
|
+
setup_global_on_error
|
77
|
+
|
78
|
+
FakeLogger.expects(:log_error).once
|
69
79
|
|
70
80
|
expected = StandardError.new(@value)
|
71
81
|
assert ToResult { raise expected } == Failure(expected)
|
72
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
|
73
113
|
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.0
|
8
|
+
s.version = '0.1.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']
|
@@ -14,4 +14,6 @@ Gem::Specification.new do |s|
|
|
14
14
|
|
15
15
|
s.require_paths = ['lib']
|
16
16
|
s.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR).reject { |f| (f == '.gitignore') || f =~ /^examples/ }
|
17
|
+
|
18
|
+
s.add_dependency 'dry-monads', '~> 1.5'
|
17
19
|
end
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: to-result
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.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-
|
12
|
-
dependencies:
|
11
|
+
date: 2022-10-27 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: dry-monads
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.5'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.5'
|
13
27
|
description: A wrapper over dry-monads to offer a handy and consistent way to implement
|
14
28
|
the Railway pattern.
|
15
29
|
email:
|