retryable 2.0.4 → 3.0.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 4adf6498d39c3a76bb6c3ea4c2ef8bd479d620b65c969cc43c970ae1dc1e8dd7
4
+ data.tar.gz: d60ff44f63519d2df8a70ca8729e5db2cda03e2b9ba2bf9af42dae66df9f65d5
5
+ SHA512:
6
+ metadata.gz: 24fc7de390a5996451a2c9d0296811b031ce21e38a3c4374d4a002e3e12d6278c07f31a84dd86efcf9006fd2ff2f7ef40e40da791f2d98dedb56ee2b0c7b3006
7
+ data.tar.gz: '09360b246ca4c6868fc40807f1fc4978933f3aae138237efdb396e51e52a36fd2c8e1a44310365336cc9d43c08df62726d9d390739e3fdcce23640abdb0b2733'
data/CHANGELOG.md CHANGED
@@ -1,8 +1,42 @@
1
+ ## Retryable 3.0.5 ##
2
+
3
+ Instead of :infinite magic constant from now on you can just use Ruby's native infinity data type e.g. Float::INFINITY.
4
+ See https://github.com/nfedyashev/retryable/commit/16f60bb09560c9470266dca8cd47c934594a67c5
5
+ This version is backwards compatible with older versions, no changes needed in your code.
6
+
7
+ ## Retryable 3.0.4 ##
8
+
9
+ Fixed typo in exception message given invalid :matching argument type https://github.com/nfedyashev/retryable/pull/29
10
+ Thanks @msroz
11
+
12
+ ## Retryable 3.0.3 ##
13
+
14
+ No changes to the source code, only added direct Changelog link on
15
+ rubygems.org for ease of use.
16
+
17
+ ## Retryable 3.0.2 ##
18
+
19
+ * :log_method param has been added for flexible logging of your retries. It is silent by default.
20
+
21
+ ## Retryable 3.0.1 ##
22
+
23
+ * :matching param from now on could be called in form of array with multiple matching conditions. This version is backwards compatible with 3.0.0
24
+
25
+ ## Retryable 3.0.0 ##
26
+ NOTE: this version is backwards compatible with 2.0.4 version unless you're running it against Ruby 1.8 version.
27
+
28
+ * retryable can now also be configured via stored contexts.
29
+ * Ruby 1.8 support has been dropped.
30
+
31
+ Thanks @chubchenko for refactoring and various improvements.
32
+
1
33
  ## Retryable 2.0.4 ##
2
34
 
3
35
  * :infinite value is now available as :tries paramater. Use it for retrying your blocks infinitely until it stops failing.
4
36
  * :sleep_method parameter has been added. This can be very useful when you are working with Celluloid which implements its own version of the method sleep.
5
- Use `:sleep_method => Celluloid.method(:sleep)` in such cases.
37
+ Use `:sleep_method: Celluloid.method(:sleep)` in such cases.
38
+
39
+ Thanks @alexcastano
6
40
 
7
41
  ## Retryable 2.0.3 ##
8
42
 
data/Gemfile ADDED
@@ -0,0 +1,20 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gem 'rake', '~> 10.4'
4
+ gem 'yard'
5
+
6
+ group :development do
7
+ gem 'fasterer'
8
+ gem 'overcommit'
9
+ gem 'pry', '= 0.9.12.6'
10
+ gem 'rubocop'
11
+ gem 'rubocop-rspec'
12
+ end
13
+
14
+ group :test do
15
+ gem 'codeclimate-test-reporter'
16
+ gem 'rspec', '~> 3.1'
17
+ gem 'simplecov', require: false
18
+ end
19
+
20
+ gemspec
data/README.md CHANGED
@@ -1,8 +1,11 @@
1
- retryable gem
2
- =====
1
+ # Retryable
3
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/retryable.svg)](https://badge.fury.io/rb/retryable)
4
4
  [![Build Status](https://travis-ci.org/nfedyashev/retryable.png?branch=master)](https://travis-ci.org/nfedyashev/retryable)
5
- [![Dependency Status](https://www.versioneye.com/ruby/retryable/badge.svg)](https://www.versioneye.com/ruby/retryable)
5
+ [![Code Climate](https://codeclimate.com/github/nfedyashev/retryable/badges/gpa.svg)](https://codeclimate.com/github/nfedyashev/retryable)
6
+ [![Test Coverage](https://codeclimate.com/github/nfedyashev/retryable/badges/coverage.svg)](https://codeclimate.com/github/nfedyashev/retryable/coverage)
7
+
8
+ [![Inline docs](http://inch-ci.org/github/nfedyashev/retryable.svg?branch=master)](http://inch-ci.org/github/nfedyashev/retryable)
6
9
 
7
10
  Description
8
11
  --------
@@ -16,6 +19,21 @@ runs the passed block. Should an exception occur, it'll retry for (n-1) times.
16
19
  Should the number of retries be reached without success, the last exception
17
20
  will be raised.
18
21
 
22
+ Installation
23
+ -------
24
+
25
+ Install the gem:
26
+
27
+ ``` bash
28
+ $ gem install retryable
29
+ ```
30
+
31
+ Add it to your Gemfile:
32
+
33
+ ``` ruby
34
+ gem 'retryable'
35
+ ```
36
+
19
37
 
20
38
  Examples
21
39
  --------
@@ -25,24 +43,26 @@ Open an URL, retry up to two times when an `OpenURI::HTTPError` occurs.
25
43
  ``` ruby
26
44
  require "open-uri"
27
45
 
28
- Retryable.retryable(:tries => 3, :on => OpenURI::HTTPError) do
46
+ Retryable.retryable(tries: 3, on: OpenURI::HTTPError) do
29
47
  xml = open("http://example.com/test.xml").read
30
48
  end
31
49
  ```
32
50
 
33
51
  Try the block forever.
34
52
  ```ruby
35
- Retryable.retryable(:tries => :infinite) do
36
- # some code
53
+ # For ruby versions prior to 1.9.2 use :infinite symbol instead
54
+ Retryable.retryable(tries: Float::INFINITY) do
55
+ # code here
37
56
  end
57
+
38
58
  ```
39
59
 
40
- Do _something_, retry up to four times for either `ArgumentError` or
41
- `TimeoutError` exceptions.
60
+ Do something, retry up to four times for either `ArgumentError` or
61
+ `Timeout::Error` exceptions.
42
62
 
43
63
  ``` ruby
44
- Retryable.retryable(:tries => 5, :on => [ArgumentError, TimeoutError]) do
45
- # some crazy code
64
+ Retryable.retryable(tries: 5, on: [ArgumentError, Timeout::Error]) do
65
+ # code here
46
66
  end
47
67
  ```
48
68
 
@@ -51,33 +71,44 @@ Ensure that block of code is executed, regardless of whether an exception was ra
51
71
  ``` ruby
52
72
  f = File.open("testfile")
53
73
 
54
- ensure_cb = Proc.new do |retries|
74
+ ensure_cb = proc do |retries|
55
75
  puts "total retry attempts: #{retries}"
56
76
 
57
77
  f.close
58
78
  end
59
79
 
60
- Retryable.retryable(:ensure => ensure_cb) do
80
+ Retryable.retryable(ensure: ensure_cb) do
61
81
  # process file
62
82
  end
63
83
  ```
64
84
 
65
85
  ## Defaults
66
86
 
67
- :tries => 2, :on => StandardError, :sleep => 1, :matching => /.*/, :ensure => Proc.new { }, :exception_cb => Proc.new { }, :not => [], :sleep_method => lambda { |n| Kernel.sleep(n) }
87
+ contexts: {},
88
+ ensure: proc { },
89
+ exception_cb: proc { },
90
+ log_method: proc { },
91
+ matching : /.*/,
92
+ not: [],
93
+ on: StandardError,
94
+ sleep: 1,
95
+ sleep_method: lambda { |n| Kernel.sleep(n) },
96
+ tries: 2
68
97
 
69
98
  Retryable also could be configured globally to change those defaults:
70
99
 
71
- ```
100
+ ```ruby
72
101
  Retryable.configure do |config|
73
- config.ensure = Proc.new {}
74
- config.exception_cb = Proc.new {}
102
+ config.contexts = {}
103
+ config.ensure = proc {}
104
+ config.exception_cb = proc {}
105
+ config.log_method = proc {}
75
106
  config.matching = /.*/
107
+ config.not = []
76
108
  config.on = StandardError
77
109
  config.sleep = 1
78
- config.tries = 2
79
- config.not = []
80
110
  config.sleep_method = Celluloid.method(:sleep)
111
+ config.tries = 2
81
112
  end
82
113
  ```
83
114
 
@@ -86,19 +117,24 @@ Sleeping
86
117
  --------
87
118
  By default Retryable waits for one second between retries. You can change this and even provide your own exponential backoff scheme.
88
119
 
89
- ```
90
- Retryable.retryable(:sleep => 0) { } # don't pause at all between retries
91
- Retryable.retryable(:sleep => 10) { } # sleep ten seconds between retries
92
- Retryable.retryable(:sleep => lambda { |n| 4**n }) { } # sleep 1, 4, 16, etc. each try
120
+ ```ruby
121
+ Retryable.retryable(sleep: 0) { } # don't pause at all between retries
122
+ Retryable.retryable(sleep: 10) { } # sleep ten seconds between retries
123
+ Retryable.retryable(sleep: lambda { |n| 4**n }) { } # sleep 1, 4, 16, etc. each try
93
124
  ```
94
125
 
95
126
  Matching error messages
96
127
  --------
97
128
  You can also retry based on the exception message:
98
129
 
99
- ```
100
- Retryable.retryable(:matching => /IO timeout/) do |retries, exception|
101
- raise "yo, IO timeout!" if retries == 0
130
+ ```ruby
131
+ Retryable.retryable(matching: /IO timeout/) do |retries, exception|
132
+ raise "oops IO timeout!" if retries == 0
133
+ end
134
+
135
+ #matching param supports array format as well:
136
+ Retryable.retryable(matching: [/IO timeout/, "IO tymeout"]) do |retries, exception|
137
+ raise "oops IO timeout!" if retries == 0
102
138
  end
103
139
  ```
104
140
 
@@ -106,31 +142,85 @@ Block Parameters
106
142
  --------
107
143
  Your block is called with two optional parameters: the number of tries until now, and the most recent exception.
108
144
 
109
- ```
145
+ ```ruby
110
146
  Retryable.retryable do |retries, exception|
111
147
  puts "try #{retries} failed with exception: #{exception}" if retries > 0
112
- pick_up_soap
148
+ # code here
113
149
  end
114
150
  ```
115
151
 
116
152
  Callback to run after an exception is rescued
117
153
  --------
118
154
 
119
- ```
120
- exception_cb = Proc.new do |exception|
155
+ ```ruby
156
+ exception_cb = proc do |exception|
121
157
  # http://smartinez87.github.io/exception_notification
122
- ExceptionNotifier.notify_exception(exception, :data => {:message => "it failed"})
158
+ ExceptionNotifier.notify_exception(exception, data: {message: "it failed"})
123
159
  end
124
160
 
125
- Retryable.retryable(:exception_cb => exception_cb) do
126
- # perform risky operation
161
+ Retryable.retryable(exception_cb: exception_cb) do
162
+ # code here
127
163
  end
128
164
  ```
129
165
 
130
- You can temporary disable retryable blocks
166
+ Logging
131
167
  --------
132
168
 
169
+ ```ruby
170
+
171
+ # or extract it to global config instead:
172
+ log_method = lambda do |retries, exception|
173
+ Logger.new(STDOUT).debug("[Attempt ##{retries}] Retrying because [#{exception.class} - #{exception.message}]: #{exception.backtrace.first(5).join(' | ')}")
174
+ end
175
+
176
+ Retryable.retryable(log_method: log_method, matching: /IO timeout/) do |retries, exception|
177
+ raise "oops IO timeout!" if retries == 0
178
+ end
179
+ #D, [2018-09-01T18:19:06.093811 #22535] DEBUG -- : [Attempt #1] Retrying because [RuntimeError - oops IO timeout!]: (irb#1):6:in `block in irb_binding' | /home/nikita/Projects/retryable/lib/retryable.rb:73:in `retryable' | (irb#1):6:in `irb_binding' | /home/nikita/.rvm/rubies/ruby-2.5.0/lib/ruby/2.5.0/irb/workspace.rb:85:in `eval' | /home/nikita/.rvm/rubies/ruby-2.5.0/lib/ruby/2.5.0/irb/workspace.rb:85:in `evaluate'
133
180
  ```
181
+
182
+ If you prefer to use Rails' native logger:
183
+
184
+ ```ruby
185
+ log_method = lambda do |retries, exception|
186
+ Rails.logger.debug("[Attempt ##{retries}] Retrying because [#{exception.class} - #{exception.message}]: #{exception.backtrace.first(5).join(' | ')}")
187
+ end
188
+ ```
189
+
190
+ Contexts
191
+ --------
192
+
193
+ Contexts allow you to extract common `Retryable.retryable` calling options for reuse or readability purposes.
194
+
195
+ ```ruby
196
+ Retryable.configure do |config|
197
+ config.contexts[:faulty_service] = {
198
+ :on: [FaultyServiceTimeoutError],
199
+ :sleep: 10,
200
+ :tries: 5
201
+ }
202
+ end
203
+
204
+
205
+ Retryable.with_context(:faulty_service) {
206
+ # code here
207
+ }
208
+ ```
209
+
210
+ You may also override options defined in your contexts:
211
+
212
+ ```ruby
213
+ # :on & sleep defined in the context earlier are still effective
214
+ Retryable.with_context(:faulty_service, tries: 999) {
215
+ # code here
216
+ }
217
+ ```
218
+
219
+
220
+ You can temporary disable retryable blocks
221
+ --------
222
+
223
+ ```ruby
134
224
  Retryable.enabled?
135
225
  => true
136
226
 
@@ -145,10 +235,10 @@ Specify exceptions where a retry should NOT be performed
145
235
  No more tries will be made if an exception listed in `:not` is raised.
146
236
  Takes precedence over `:on`.
147
237
 
148
- ```
238
+ ```ruby
149
239
  class MyError < StandardError; end
150
240
 
151
- Retryable.retryable(:tries => 5, :on => [StandardError], :not => [MyError]) do
241
+ Retryable.retryable(tries: 5, on: [StandardError], not: [MyError]) do
152
242
  raise MyError "No retries!"
153
243
  end
154
244
 
@@ -159,9 +249,9 @@ Specify the sleep method to use
159
249
  This can be very useful when you are working with [Celluloid](https://github.com/celluloid/celluloid)
160
250
  which implements its own version of the method sleep.
161
251
 
162
- ```
163
- Retryable.retryable(:sleep_method => Celluloid.method(:sleep)) do
164
- retrieve_url
252
+ ```ruby
253
+ Retryable.retryable(sleep_method: Celluloid.method(:sleep)) do
254
+ # code here
165
255
  end
166
256
  ```
167
257
 
@@ -171,12 +261,16 @@ Supported Ruby Versions
171
261
  This library aims to support and is [tested against][travis] the following Ruby
172
262
  versions:
173
263
 
174
- * Ruby 1.8.7
175
- * Ruby 1.9.2
176
264
  * Ruby 1.9.3
177
265
  * Ruby 2.0.0
178
- * Ruby 2.1.2
179
- * Ruby 2.2.0
266
+ * Ruby 2.1.10
267
+ * Ruby 2.2.10
268
+ * Ruby 2.3.8
269
+ * Ruby 2.4.5
270
+ * Ruby 2.5.3
271
+ * Ruby 2.6.1
272
+
273
+ *NOTE: if you need `retryable` to be running on Ruby 1.8 use gem versions prior to 3.0.0 release*
180
274
 
181
275
  If something doesn't work on one of these versions, it's a bug.
182
276
 
@@ -185,27 +279,3 @@ however support will only be provided for the versions listed above.
185
279
 
186
280
  If you would like this library to support another Ruby version or
187
281
  implementation, you may volunteer to be a maintainer.
188
-
189
-
190
- Installation
191
- -------
192
-
193
- Install the gem:
194
-
195
- ``` bash
196
- $ gem install retryable
197
- ```
198
-
199
- Add it to your Gemfile:
200
-
201
- ``` ruby
202
- gem 'retryable'
203
- ```
204
-
205
- ## Thanks
206
-
207
- [Chu Yeow for this nifty piece of code](http://blog.codefront.net/2008/01/14/retrying-code-blocks-in-ruby-on-exceptions-whatever/)
208
-
209
- [Scott Bronson](https://github.com/bronson/retryable)
210
-
211
- [travis]: http://travis-ci.org/nfedyashev/retryable
data/Rakefile CHANGED
@@ -4,8 +4,8 @@ Bundler::GemHelper.install_tasks
4
4
  require 'rspec/core/rake_task'
5
5
  RSpec::Core::RakeTask.new(:spec)
6
6
 
7
- task :test => :spec
8
- task :default => :spec
7
+ task test: :spec
8
+ task default: :spec
9
9
 
10
10
  require 'yard'
11
11
  YARD::Rake::YardocTask.new
@@ -1,45 +1,42 @@
1
1
  module Retryable
2
2
  # Used to set up and modify settings for the retryable.
3
3
  class Configuration
4
- OPTIONS = [
4
+ VALID_OPTION_KEYS = [
5
+ :contexts,
5
6
  :ensure,
6
7
  :exception_cb,
8
+ :log_method,
7
9
  :matching,
10
+ :not,
8
11
  :on,
9
12
  :sleep,
10
- :tries,
11
- :not,
12
- :sleep_method
13
+ :sleep_method,
14
+ :tries
13
15
  ].freeze
14
16
 
15
- attr_accessor :ensure
16
- attr_accessor :exception_cb
17
- attr_accessor :matching
18
- attr_accessor :on
19
- attr_accessor :sleep
20
- attr_accessor :tries
21
- attr_accessor :not
22
- attr_accessor :sleep_method
17
+ attr_accessor(*VALID_OPTION_KEYS)
23
18
 
24
19
  attr_accessor :enabled
25
20
 
26
- alias_method :enabled?, :enabled
27
-
28
21
  def initialize
29
- @ensure = Proc.new {}
30
- @exception_cb = Proc.new {}
22
+ @contexts = {}
23
+ @ensure = proc {}
24
+ @exception_cb = proc {}
25
+ @log_method = proc {}
31
26
  @matching = /.*/
27
+ @not = []
32
28
  @on = StandardError
33
29
  @sleep = 1
30
+ @sleep_method = ->(seconds) { Kernel.sleep(seconds) }
34
31
  @tries = 2
35
- @not = []
36
- @sleep_method = lambda do |seconds| Kernel.sleep(seconds) end
37
- @enabled = true
32
+
33
+ @enabled = true
38
34
  end
39
35
 
40
36
  def enable
41
37
  @enabled = true
42
38
  end
39
+ alias enabled? enabled
43
40
 
44
41
  def disable
45
42
  @enabled = false
@@ -54,9 +51,8 @@ module Retryable
54
51
 
55
52
  # Returns a hash of all configurable options
56
53
  def to_hash
57
- OPTIONS.inject({}) do |hash, option|
58
- hash[option.to_sym] = self.send(option)
59
- hash
54
+ VALID_OPTION_KEYS.each_with_object({}) do |key, memo|
55
+ memo[key] = instance_variable_get("@#{key}")
60
56
  end
61
57
  end
62
58
 
@@ -1,16 +1,40 @@
1
1
  module Retryable
2
- class Version
3
- MAJOR = 2 unless defined? Retryable::Version::MAJOR
4
- MINOR = 0 unless defined? Retryable::Version::MINOR
5
- PATCH = 4 unless defined? Retryable::Version::PATCH
2
+ # This module holds the Retryable version information.
3
+ module Version
4
+ module_function
6
5
 
7
- class << self
6
+ # @return [Integer]
7
+ def major
8
+ 3
9
+ end
10
+
11
+ # @return [Integer]
12
+ def minor
13
+ 0
14
+ end
15
+
16
+ # @return [Integer]
17
+ def patch
18
+ 5
19
+ end
20
+
21
+ # @return [Hash]
22
+ def to_h
23
+ {
24
+ major: major,
25
+ minor: minor,
26
+ patch: patch
27
+ }
28
+ end
8
29
 
9
- # @return [String]
10
- def to_s
11
- [MAJOR, MINOR, PATCH].compact.join('.')
12
- end
30
+ # @return [Hash]
31
+ def to_a
32
+ [major, minor, patch].compact
33
+ end
13
34
 
35
+ # @return [String]
36
+ def to_s
37
+ to_a.join('.')
14
38
  end
15
39
  end
16
40
  end
data/lib/retryable.rb CHANGED
@@ -1,8 +1,12 @@
1
1
  require 'retryable/version'
2
2
  require 'retryable/configuration'
3
+ require 'forwardable'
3
4
 
5
+ # Runs a code block, and retries it when an exception occurs. It's great when working with flakey webservices (for example).
4
6
  module Retryable
5
7
  class << self
8
+ extend Forwardable
9
+
6
10
  # A Retryable configuration object. Must act like a hash and return sensible
7
11
  # values for all Retryable configuration options. See Retryable::Configuration.
8
12
  attr_writer :configuration
@@ -11,13 +15,16 @@ module Retryable
11
15
  #
12
16
  # @example
13
17
  # Retryable.configure do |config|
14
- # config.ensure = Proc.new {}
15
- # config.exception_cb = Proc.new {}
18
+ # config.contexts = {}
19
+ # config.ensure = proc {}
20
+ # config.exception_cb = proc {}
21
+ # config.log_method = proc {}
16
22
  # config.matching = /.*/
23
+ # config.not = []
17
24
  # config.on = StandardError
18
25
  # config.sleep = 1
26
+ # config.sleep_method = ->(seconds) { Kernel.sleep(seconds) }
19
27
  # config.tries = 2
20
- # config.not = []
21
28
  # end
22
29
  def configure
23
30
  yield(configuration)
@@ -29,49 +36,48 @@ module Retryable
29
36
  @configuration ||= Configuration.new
30
37
  end
31
38
 
32
- def enabled?
33
- configuration.enabled?
34
- end
39
+ delegate [:enabled?, :enable, :disable] => :configuration
35
40
 
36
- def enable
37
- configuration.enable
41
+ def with_context(context_key, options = {}, &block)
42
+ unless configuration.contexts.key?(context_key)
43
+ raise ArgumentError, "#{context_key} not found in Retryable.configuration.contexts. Available contexts: #{configuration.contexts.keys}"
44
+ end
45
+ retryable(configuration.contexts[context_key].merge(options), &block) if block
38
46
  end
39
47
 
40
- def disable
41
- configuration.disable
42
- end
48
+ alias retryable_with_context with_context
43
49
 
44
- def retryable(options = {}, &block)
45
- opts = {
46
- :tries => self.configuration.tries,
47
- :sleep => self.configuration.sleep,
48
- :on => self.configuration.on,
49
- :matching => self.configuration.matching,
50
- :ensure => self.configuration.ensure,
51
- :exception_cb => self.configuration.exception_cb,
52
- :not => self.configuration.not,
53
- :sleep_method => self.configuration.sleep_method
54
- }
50
+ # rubocop:disable Metrics/MethodLength
51
+ # rubocop:disable Metrics/PerceivedComplexity
52
+ def retryable(options = {})
53
+ opts = configuration.to_hash
55
54
 
56
55
  check_for_invalid_options(options, opts)
57
56
  opts.merge!(options)
58
57
 
58
+ # rubocop:disable Style/NumericPredicate
59
59
  return if opts[:tries] == 0
60
+ # rubocop:enable Style/NumericPredicate
60
61
 
61
- on_exception = [ opts[:on] ].flatten
62
- not_exception = [ opts[:not] ].flatten
62
+ on_exception = opts[:on].is_a?(Array) ? opts[:on] : [opts[:on]]
63
+ not_exception = opts[:not].is_a?(Array) ? opts[:not] : [opts[:not]]
64
+
65
+ matching = opts[:matching].is_a?(Array) ? opts[:matching] : [opts[:matching]]
63
66
  tries = opts[:tries]
64
67
  retries = 0
65
68
  retry_exception = nil
66
69
 
67
70
  begin
71
+ opts[:log_method].call(retries, retry_exception) if retries > 0
68
72
  return yield retries, retry_exception
69
73
  rescue *not_exception
70
74
  raise
71
75
  rescue *on_exception => exception
72
76
  raise unless configuration.enabled?
73
- raise unless exception.message =~ opts[:matching]
74
- raise if tries != :infinite && retries+1 >= tries
77
+ raise unless matches?(exception.message, matching)
78
+
79
+ infinite_retries = :infinite || tries.respond_to?(:infinite?) && tries.infinite?
80
+ raise if tries != infinite_retries && retries + 1 >= tries
75
81
 
76
82
  # Interrupt Exception could be raised while sleeping
77
83
  begin
@@ -90,13 +96,28 @@ module Retryable
90
96
  opts[:ensure].call(retries)
91
97
  end
92
98
  end
99
+ # rubocop:enable Metrics/MethodLength
100
+ # rubocop:enable Metrics/PerceivedComplexity
93
101
 
94
102
  private
95
103
 
96
104
  def check_for_invalid_options(custom_options, default_options)
97
105
  invalid_options = default_options.merge(custom_options).keys - default_options.keys
106
+ return if invalid_options.empty?
107
+ raise ArgumentError, "[Retryable] Invalid options: #{invalid_options.join(', ')}"
108
+ end
98
109
 
99
- raise ArgumentError.new("[Retryable] Invalid options: #{invalid_options.join(", ")}") unless invalid_options.empty?
110
+ def matches?(message, candidates)
111
+ candidates.any? do |candidate|
112
+ case candidate
113
+ when String
114
+ message.include?(candidate)
115
+ when Regexp
116
+ message =~ candidate
117
+ else
118
+ raise ArgumentError, ':matching must be a string or regex'
119
+ end
120
+ end
100
121
  end
101
122
  end
102
123
  end