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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6db91d218609e2e2694dbc1ef6754d8e7c9951d27b8bb73ad521c856db4b9c75
4
- data.tar.gz: 90f313139ed88c00b1c1877c4d8c94d3629d2d1662fee8315a64e65894ea81d4
3
+ metadata.gz: bc8202a00d4cc8938cac602f09b0a9b0694a29421bedb0c71be82f94fad66e89
4
+ data.tar.gz: 783334b0487c880174639c699eb61fc6eacd695086a5c5b9b531947bd1645b30
5
5
  SHA512:
6
- metadata.gz: 4113a67772569963f5b8c4f9711168903aab0a16daddf4d00a2fb1ef288651989b46b64a07e8d4b97fd8ab83e3df716c9f2cd850fb8eecb350ae30ad68359fac
7
- data.tar.gz: 0604e31717e86753ff06df3bda5754b11916bb27b5f5b8b0b22a3d2ad73a98d3269ba4f0da3ba81c73247161815118a22e580ac6f69538555788e091052fd335
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
@@ -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.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.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
@@ -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` 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
 
@@ -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
- - [ ] configurable error logging when an exception is catched inside `DoResult`
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
- - [ ] transform/process the catched error
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 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
51
  error = e.result
37
- @@configuration.on_error.call(error) if @@configuration.on_error.respond_to?(:call)
52
+ on_error.call(error) if on_error.respond_to?(:call)
38
53
  return error
39
- rescue *exceptions => e
40
- @@configuration.on_error.call(e) if @@configuration.on_error.respond_to?(:call)
54
+ rescue *only => e
55
+ on_error.call(e) if on_error.respond_to?(:call)
41
56
  raise e
42
57
  end
43
- ).to_result
58
+
59
+ Try.run(only, f_wrapper).to_result
44
60
  end
45
61
  end
@@ -2,4 +2,8 @@ class FakeLogger
2
2
  def self.log_error
3
3
  true
4
4
  end
5
+
6
+ def self.return_error(e)
7
+ e
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,9 +56,13 @@ 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
@@ -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.4'
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
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-08 00:00:00.000000000 Z
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: