to-result 0.0.4 → 0.1.1

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: 6db91d218609e2e2694dbc1ef6754d8e7c9951d27b8bb73ad521c856db4b9c75
4
- data.tar.gz: 90f313139ed88c00b1c1877c4d8c94d3629d2d1662fee8315a64e65894ea81d4
3
+ metadata.gz: fc48c861de210a05cba164c61710457447f04b60b492ef15791ab7e5c28a3375
4
+ data.tar.gz: fb5802866e75958e710ec9a0be23584f8f737101ef8afd18c5b212a775539c1c
5
5
  SHA512:
6
- metadata.gz: 4113a67772569963f5b8c4f9711168903aab0a16daddf4d00a2fb1ef288651989b46b64a07e8d4b97fd8ab83e3df716c9f2cd850fb8eecb350ae30ad68359fac
7
- data.tar.gz: 0604e31717e86753ff06df3bda5754b11916bb27b5f5b8b0b22a3d2ad73a98d3269ba4f0da3ba81c73247161815118a22e580ac6f69538555788e091052fd335
6
+ metadata.gz: 60728e022ce553988082effc1ab86a3260cf399a464bddea06d936e6428bf54f0604a8b86638ee7932526a20d72b3c80bcc491f6d816d6d17bdd93326c11b2ce
7
+ data.tar.gz: 959d7af9b3eb1367269c6650ca9c9babedd8c3cfd6e36403d3d4f01b750219dc7781cec78e4bc2a4972ce34e230d157dbd17a8bee70b7c9cc24c407fb71f5bc3
data/CHANGELOG.md CHANGED
@@ -1,4 +1,25 @@
1
1
  # Changelog
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
+
9
+ ## [0.1.0](https://github.com/a-chris/to-result/tree/0.1.0) (2022-10-28)
10
+
11
+ - Explicit requires `dry-monads` >= 1.x, just for convenience
12
+
13
+ BREAKING CHANGES:
14
+
15
+ - The arrays of exceptions to catch is now received using the `only` argument key, e.g. `ToResult(only: [ArgumentError])`
16
+ - Setting the global `on_error` with a non-callable object (an object that does not respond to `call`) will raise `TypeError`
17
+
18
+ NEW FEATURES:
19
+
20
+ - Added local `on_error` that can be used to override or run a one-time block when an error is catched
21
+ `ToResult(on_error: Proc { |e| puts e })`
22
+
2
23
  ## [0.0.4](https://github.com/a-chris/to-result/tree/0.0.4) (2022-10-08)
3
24
 
4
25
  - Adds a global `configuration` object for ToResult
@@ -20,6 +41,4 @@
20
41
 
21
42
  [Full Changelog](https://github.com/a-chris/to-result/compare/8dce552d6d07a2a145c45dbf7d05dbe6b0c5c578...0.0.1)
22
43
 
23
-
24
-
25
- \* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*
44
+ \* _This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)_
data/Gemfile CHANGED
@@ -5,7 +5,7 @@ source 'https://rubygems.org'
5
5
  # Specify your gem's dependencies in pulsarcli.gemspec
6
6
  gemspec
7
7
 
8
- gem 'dry-monads', '~> 1.4'
8
+ gem 'dry-monads', '~> 1.5'
9
9
 
10
10
  gem 'byebug', '~> 11.1'
11
11
 
data/Gemfile.lock CHANGED
@@ -1,27 +1,31 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- to-result (0.0.4)
4
+ to-result (0.1.1)
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.8.1)
12
+ dry-core (0.9.1)
12
13
  concurrent-ruby (~> 1.0)
13
- dry-monads (1.4.0)
14
+ zeitwerk (~> 2.6)
15
+ dry-monads (1.5.0)
14
16
  concurrent-ruby (~> 1.0)
15
- dry-core (~> 0.7)
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.4)
28
+ dry-monads (~> 1.5)
25
29
  minitest (~> 5.16)
26
30
  mocha (~> 1.15)
27
31
  to-result!
data/README.md CHANGED
@@ -1,11 +1,11 @@
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
 
8
- `dry-monads` is full of edge cases that require to write boilerplate code everytime I want a method to return a `Success` or `Failure`, for example:
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
- so I started using `Try`, that makes the code easier to read and faster to write:
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 code and if you forget to write it (as always happens to me) your application blows up.
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 bigget problem, bear with me.
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 `Do Notation` inside a `Try` block:
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 exception will be forever lost if we do not "unbox" the exception with `e.result`.
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
 
@@ -97,7 +120,7 @@ class MyClass
97
120
  end
98
121
  ```
99
122
 
100
- 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.
101
124
 
102
125
  Look at this:
103
126
 
@@ -114,20 +137,48 @@ 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
+ ## 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
+
171
+ ## Changelog
172
+
173
+ [Changelog](CHANGELOG.md)
174
+
124
175
  ## Roadmap
125
176
  I'm already planning to implement some useful features:
126
177
  - [x] write more examples/documentation/tests
127
- - [ ] configurable error logging when an exception is catched inside `DoResult`
178
+ - [x] configurable error logging when an exception is catched inside `DoResult`
128
179
  e.g. sending the log to Airbrake or whathever service you are using
129
- - [ ] transform/process the catched error
130
- - [ ] any other suggestion would be appreciated 😁
180
+ - [x] transform/process the catched error => this can be handled with `alt_map` or other methods already available in `dry-monads`
181
+ - [ ] any type of suggestion is appreciated 😁
131
182
 
132
183
  ## Authors
133
184
 
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,38 @@ 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 to catch.
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
- # @param [Array<Class>] exceptions
25
- # @param [Proc] &f
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(exceptions = [StandardError], &f)
31
- Try.run(
32
- exceptions,
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
- error = e.result
37
- @@configuration.on_error.call(error) if @@configuration.on_error.respond_to?(:call)
38
- return error
39
- rescue *exceptions => e
40
- @@configuration.on_error.call(e) if @@configuration.on_error.respond_to?(:call)
51
+ failure = error = e.result
52
+ error = error.failure if error.respond_to?(:failure)
53
+ on_error.call(error) if on_error.respond_to?(:call)
54
+ return failure
55
+ rescue *only => e
56
+ on_error.call(e) if on_error.respond_to?(:call)
41
57
  raise e
42
58
  end
43
- ).to_result
59
+
60
+ Try.run(only, f_wrapper).to_result
44
61
  end
45
62
  end
@@ -1,5 +1,9 @@
1
1
  class FakeLogger
2
- def self.log_error
3
- true
2
+ def self.log_error(e)
3
+ e
4
+ end
5
+
6
+ def self.return_error(e)
7
+ e
4
8
  end
5
9
  end
@@ -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,18 +56,59 @@ class ToResultTest < Minitest::Test
56
56
  assert ToResult { yield expected } == expected
57
57
  end
58
58
 
59
- def test_on_error
60
- FakeLogger.expects(:log_error).once
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
65
69
  ToResultMixin.configure do |c|
66
- c.on_error = Proc.new { FakeLogger.log_error }
70
+ c.on_error = Proc.new { |e| FakeLogger.log_error(e) }
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)
81
+
71
82
  assert ToResult { raise expected } == Failure(expected)
72
83
  end
84
+
85
+ def test_local_on_error_overrides_global
86
+ setup_global_on_error
87
+
88
+ FakeLogger.expects(:log_error).once
89
+
90
+ local_on_error = Proc.new { FakeLogger.log_error }
91
+
92
+ expected = StandardError.new(@value)
93
+ assert ToResult(on_error: local_on_error) { raise expected } == Failure(expected)
94
+ end
95
+
96
+ def test_local_on_error_overrides_global_nil
97
+ setup_global_on_error
98
+
99
+ FakeLogger.expects(:log_error).never
100
+
101
+ local_on_error = nil
102
+
103
+ expected = StandardError.new(@value)
104
+ assert ToResult(on_error: local_on_error) { raise expected } == Failure(expected)
105
+ end
106
+
107
+ def test_local_on_error_without_global
108
+ local_on_error = Proc.new { |e| FakeLogger.return_error(e) }
109
+
110
+ expected = StandardError.new(@value)
111
+ FakeLogger.expects(:return_error).with(expected).returns(expected).once
112
+ assert ToResult(on_error: local_on_error) { yield Failure(expected) } == Failure(expected)
113
+ end
73
114
  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.4'
8
+ s.version = '0.1.1'
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
4
+ version: 0.1.1
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-08 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2023-08-16 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:
@@ -46,7 +60,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
46
60
  - !ruby/object:Gem::Version
47
61
  version: '0'
48
62
  requirements: []
49
- rubygems_version: 3.2.22
63
+ rubygems_version: 3.4.10
50
64
  signing_key:
51
65
  specification_version: 4
52
66
  summary: A wrapper over dry-monads to offer a handy and consistent way to implement