to-result 0.0.3 → 0.1.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: 842070fb5be7a4388055318b3956091d4869885c76d745c0dd01ba87cde415e5
4
- data.tar.gz: 0a7c4e93685409f3f96479df8482255e501a88db53523c55d2f5f171255f58a3
3
+ metadata.gz: bc8202a00d4cc8938cac602f09b0a9b0694a29421bedb0c71be82f94fad66e89
4
+ data.tar.gz: 783334b0487c880174639c699eb61fc6eacd695086a5c5b9b531947bd1645b30
5
5
  SHA512:
6
- metadata.gz: c16327c7a1420c8c1b711d98ffd59de246a3221c98dd6a3bae2cf3650c731c558ccbb02effccdd25d6f9f99e0097d030d751cf8c1dac40569b97dd95b4845817
7
- data.tar.gz: 23d78aac99bc431eb9b47aafe9aca8dde5b95615766a60305ce016638a3c26e74ac632f5c19bf9c913a5ce70a2ba14d187dc3aea99f8c5876155db4ad9cbed34
6
+ metadata.gz: 4e2b40217e547f2cabbcc84847f1f257676863997c088f4e2c43416a608fd804b07bf7586e95d6ab60b8c70fa501b6cd297deb7d9ad7a86f0acade0977ef5f64
7
+ data.tar.gz: feceedd4425f782ee47637da08a69fbef6f54b226a5bd104e6a8f22ea219a9b4be1fd30ffac56e88df0ec1aa2212f4c2ea2994b81f51155f8d1dae8ff29fb41b
data/CHANGELOG.md ADDED
@@ -0,0 +1,38 @@
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
+
17
+ ## [0.0.4](https://github.com/a-chris/to-result/tree/0.0.4) (2022-10-08)
18
+
19
+ - Adds a global `configuration` object for ToResult
20
+ - Adds `on_error` field in `configuration` to execute a block every time a error is catched
21
+
22
+ [Full Changelog](https://github.com/a-chris/to-result/compare/0.0.3...0.0.4)
23
+
24
+ ## [0.0.3](https://github.com/a-chris/to-result/tree/0.0.3) (2022-10-08)
25
+
26
+ - Fixed file name, `require 'to_result'` is not needed anymore to make this gem work.
27
+
28
+ [Full Changelog](https://github.com/a-chris/to-result/compare/0.0.2...0.0.3)
29
+
30
+ ## [0.0.2](https://github.com/a-chris/to-result/tree/0.0.2) (2022-09-18)
31
+
32
+ [Full Changelog](https://github.com/a-chris/to-result/compare/0.0.1...0.0.2)
33
+
34
+ ## [0.0.1](https://github.com/a-chris/to-result/tree/0.0.1) (2022-09-18)
35
+
36
+ [Full Changelog](https://github.com/a-chris/to-result/compare/8dce552d6d07a2a145c45dbf7d05dbe6b0c5c578...0.0.1)
37
+
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.3)
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
@@ -3,9 +3,17 @@ require 'dry/monads'
3
3
  module ToResultMixin
4
4
  include Dry::Monads[:do, :result, :try]
5
5
 
6
- Configuration = Struct.new(:on_error)
6
+ class Configuration
7
+ attr_accessor :on_error
7
8
 
8
- @@configuration = Configuration.new(on_error: nil)
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
14
+ end
15
+
16
+ @@configuration = Configuration.new
9
17
 
10
18
  #
11
19
  # Allow to override the @@configuration fields
@@ -17,27 +25,37 @@ module ToResultMixin
17
25
  #
18
26
  # ToResult executes a block of code and returns Success or Failure.
19
27
  # All exceptions inherited from StandardError are catched and
20
- # 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.
21
33
  #
22
- # @param [Array<Class>] exceptions
23
- # @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
24
38
  #
25
39
  # @return [Success]
26
40
  # @return [Failure]
27
41
  #
28
- def ToResult(exceptions = [StandardError], &f)
29
- Try.run(
30
- 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 =
31
48
  Proc.new do
32
49
  f.call
33
50
  rescue Dry::Monads::Do::Halt => e
34
51
  error = e.result
35
- @@configuration.on_error.call(error) if @@configuration.on_error.respond_to?(:call)
52
+ on_error.call(error) if on_error.respond_to?(:call)
36
53
  return error
37
- rescue *exceptions => e
38
- @@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)
39
56
  raise e
40
57
  end
41
- ).to_result
58
+
59
+ Try.run(only, f_wrapper).to_result
42
60
  end
43
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,13 +56,58 @@ 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
 
62
- ToResultMixin.configure do |c|
63
- c.on_error = Proc.new { FakeLogger.log_error }
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
64
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
65
99
 
66
- ToResult { raise StandardError.new(@value) }
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)
67
112
  end
68
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.3'
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.3
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:
@@ -17,6 +31,7 @@ executables: []
17
31
  extensions: []
18
32
  extra_rdoc_files: []
19
33
  files:
34
+ - CHANGELOG.md
20
35
  - Gemfile
21
36
  - Gemfile.lock
22
37
  - LICENSE