hearth 1.0.0.pre1

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.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +12 -0
  3. data/VERSION +1 -0
  4. data/lib/hearth/api_error.rb +15 -0
  5. data/lib/hearth/block_io.rb +24 -0
  6. data/lib/hearth/context.rb +34 -0
  7. data/lib/hearth/http/api_error.rb +29 -0
  8. data/lib/hearth/http/client.rb +152 -0
  9. data/lib/hearth/http/error_parser.rb +105 -0
  10. data/lib/hearth/http/headers.rb +70 -0
  11. data/lib/hearth/http/middleware/content_length.rb +29 -0
  12. data/lib/hearth/http/networking_error.rb +20 -0
  13. data/lib/hearth/http/request.rb +132 -0
  14. data/lib/hearth/http/response.rb +29 -0
  15. data/lib/hearth/http.rb +36 -0
  16. data/lib/hearth/json/parse_error.rb +18 -0
  17. data/lib/hearth/json.rb +30 -0
  18. data/lib/hearth/middleware/around_handler.rb +24 -0
  19. data/lib/hearth/middleware/build.rb +26 -0
  20. data/lib/hearth/middleware/host_prefix.rb +48 -0
  21. data/lib/hearth/middleware/parse.rb +42 -0
  22. data/lib/hearth/middleware/request_handler.rb +24 -0
  23. data/lib/hearth/middleware/response_handler.rb +25 -0
  24. data/lib/hearth/middleware/retry.rb +43 -0
  25. data/lib/hearth/middleware/send.rb +62 -0
  26. data/lib/hearth/middleware/validate.rb +29 -0
  27. data/lib/hearth/middleware.rb +16 -0
  28. data/lib/hearth/middleware_builder.rb +246 -0
  29. data/lib/hearth/middleware_stack.rb +73 -0
  30. data/lib/hearth/number_helper.rb +33 -0
  31. data/lib/hearth/output.rb +20 -0
  32. data/lib/hearth/structure.rb +40 -0
  33. data/lib/hearth/stubbing/client_stubs.rb +115 -0
  34. data/lib/hearth/stubbing/stubs.rb +32 -0
  35. data/lib/hearth/time_helper.rb +35 -0
  36. data/lib/hearth/union.rb +10 -0
  37. data/lib/hearth/validator.rb +20 -0
  38. data/lib/hearth/waiters/errors.rb +15 -0
  39. data/lib/hearth/waiters/poller.rb +132 -0
  40. data/lib/hearth/waiters/waiter.rb +79 -0
  41. data/lib/hearth/xml/formatter.rb +68 -0
  42. data/lib/hearth/xml/node.rb +123 -0
  43. data/lib/hearth/xml/node_matcher.rb +24 -0
  44. data/lib/hearth/xml/parse_error.rb +18 -0
  45. data/lib/hearth/xml.rb +58 -0
  46. data/lib/hearth.rb +26 -0
  47. data/sig/lib/seahorse/api_error.rbs +10 -0
  48. data/sig/lib/seahorse/document.rbs +2 -0
  49. data/sig/lib/seahorse/http/api_error.rbs +21 -0
  50. data/sig/lib/seahorse/http/headers.rbs +47 -0
  51. data/sig/lib/seahorse/http/response.rbs +21 -0
  52. data/sig/lib/seahorse/simple_delegator.rbs +3 -0
  53. data/sig/lib/seahorse/structure.rbs +18 -0
  54. data/sig/lib/seahorse/stubbing/client_stubs.rbs +103 -0
  55. data/sig/lib/seahorse/stubbing/stubs.rbs +14 -0
  56. data/sig/lib/seahorse/union.rbs +6 -0
  57. metadata +111 -0
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cgi'
4
+ require_relative 'http/api_error'
5
+ require_relative 'http/client'
6
+ require_relative 'http/error_parser'
7
+ require_relative 'http/headers'
8
+ require_relative 'http/middleware/content_length'
9
+ require_relative 'http/networking_error'
10
+ require_relative 'http/request'
11
+ require_relative 'http/response'
12
+
13
+ module Hearth
14
+ # HTTP namespace for HTTP specific functionality. Also includes utility
15
+ # methods for URI escaping.
16
+ # @api private
17
+ module HTTP
18
+ # TODO: - do these belong here?
19
+ class << self
20
+ # URI escapes the given value.
21
+ #
22
+ # Hearth::_escape("a b/c")
23
+ # #=> "a%20b%2Fc"
24
+ #
25
+ # @param [String] value
26
+ # @return [String] URI encoded value except for '+' and '~'.
27
+ def uri_escape(value)
28
+ CGI.escape(value.encode('UTF-8')).gsub('+', '%20').gsub('%7E', '~')
29
+ end
30
+
31
+ def uri_escape_path(path)
32
+ path.gsub(%r{[^/]+}) { |part| uri_escape(part) }
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hearth
4
+ module JSON
5
+ # An error class encountered when parsing JSON.
6
+ class ParseError < StandardError
7
+ MSG = 'Encountered an error while parsing the response: %<message>s'
8
+
9
+ def initialize(original_error)
10
+ @original_error = original_error
11
+ super(format(MSG, message: original_error.message))
12
+ end
13
+
14
+ # @return [StandardError]
15
+ attr_reader :original_error
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'json/parse_error'
4
+ require 'json'
5
+
6
+ module Hearth
7
+ # Hearth::JSON is a purpose-built set of utilities for working with
8
+ # JSON. It does not support many/most features of generic JSON
9
+ # parsing and serialization.
10
+ # @api private
11
+ module JSON
12
+ class << self
13
+ # @param [String] json
14
+ # @return [Hash]
15
+ def load(json)
16
+ # rubocop:disable Security/JSONLoad
17
+ ::JSON.load(json)
18
+ # rubocop:enable Security/JSONLoad
19
+ rescue ::JSON::ParserError => e
20
+ raise ParseError, e
21
+ end
22
+
23
+ # @param [Hash] value
24
+ # @return [String] json
25
+ def dump(value)
26
+ ::JSON.dump(value)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hearth
4
+ module Middleware
5
+ # A class used to register middleware around a request.
6
+ # @api private
7
+ class AroundHandler
8
+ # @param [Class] app The next middleware in the stack.
9
+ # @param [Proc] handler A proc object that is called around the request.
10
+ # The proc must return the next middleware.
11
+ def initialize(app, handler:)
12
+ @app = app
13
+ @handler = handler
14
+ end
15
+
16
+ # @param input
17
+ # @param context
18
+ # @return [Output]
19
+ def call(input, context)
20
+ @handler.call(@app, input, context)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hearth
4
+ module Middleware
5
+ # A middleware that builds a request object.
6
+ # @api private
7
+ class Build
8
+ # @param [Class] app The next middleware in the stack.
9
+ # @param [Class] builder A builder object responsible for building the
10
+ # request. It must respond to #build and take the request and input as
11
+ # arguments.
12
+ def initialize(app, builder:)
13
+ @app = app
14
+ @builder = builder
15
+ end
16
+
17
+ # @param input
18
+ # @param context
19
+ # @return [Output]
20
+ def call(input, context)
21
+ @builder.build(context.request, input: input)
22
+ @app.call(input, context)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hearth
4
+ module Middleware
5
+ # A middleware that prefixes the host.
6
+ # @api private
7
+ class HostPrefix
8
+ # @param [Class] app The next middleware in the stack.
9
+ # @param [Boolean] disable_host_prefix If true, this option will not
10
+ # modify the host url.
11
+ # @param [String] host_prefix The prefix for the host. It may contain
12
+ # labels populated by input (i.e. \\{foo.} would be populated by
13
+ # input[:foo]).
14
+ def initialize(app, disable_host_prefix:, host_prefix:)
15
+ @app = app
16
+ @disable_host_prefix = disable_host_prefix
17
+ @host_prefix = host_prefix
18
+ end
19
+
20
+ # @param input
21
+ # @param context
22
+ # @return [Output]
23
+ def call(input, context)
24
+ unless @disable_host_prefix
25
+ prefix = apply_labels(@host_prefix, input)
26
+ context.request.prefix_host(prefix)
27
+ end
28
+ @app.call(input, context)
29
+ end
30
+
31
+ private
32
+
33
+ def apply_labels(host_prefix, input)
34
+ host_prefix.gsub(/\{.+?\}/) do |host_label|
35
+ key = host_label.delete('{}')
36
+ value = input[key.to_sym]
37
+
38
+ if value.nil? || value.empty?
39
+ raise ArgumentError,
40
+ "Host label #{key} cannot be nil or empty."
41
+ end
42
+
43
+ value
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hearth
4
+ module Middleware
5
+ # A middleware that parses a response object.
6
+ # @api private
7
+ class Parse
8
+ # @param [Class] app The next middleware in the stack.
9
+ # @param [Class] error_parser A parser object responsible for parsing the
10
+ # response if there is an error. It must respond to #parse and take the
11
+ # response as an argument.
12
+ # @param [Class] data_parser A parser object responsible for parsing the
13
+ # response if there is data. It must respond to #parse and take the
14
+ # response as an argument.
15
+ def initialize(app, error_parser:, data_parser:)
16
+ @app = app
17
+ @error_parser = error_parser
18
+ @data_parser = data_parser
19
+ end
20
+
21
+ # @param input
22
+ # @param context
23
+ # @return [Output]
24
+ def call(input, context)
25
+ output = @app.call(input, context)
26
+ parse_error(context.response, output) unless output.error
27
+ parse_data(context.response, output) unless output.error
28
+ output
29
+ end
30
+
31
+ private
32
+
33
+ def parse_error(response, output)
34
+ output.error = @error_parser.parse(response)
35
+ end
36
+
37
+ def parse_data(response, output)
38
+ output.data = @data_parser.parse(response)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hearth
4
+ module Middleware
5
+ # A class used to register middleware before a request is sent.
6
+ # @api private
7
+ class RequestHandler
8
+ # @param [Class] app The next middleware in the stack.
9
+ # @param [Proc] handler A proc object that is called before the request.
10
+ def initialize(app, handler:)
11
+ @app = app
12
+ @handler = handler
13
+ end
14
+
15
+ # @param input
16
+ # @param context
17
+ # @return [Output]
18
+ def call(input, context)
19
+ @handler.call(input, context)
20
+ @app.call(input, context)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hearth
4
+ module Middleware
5
+ # A class used to register middleware after a request is sent.
6
+ # @api private
7
+ class ResponseHandler
8
+ # @param [Class] app The next middleware in the stack.
9
+ # @param [Proc] handler A proc object that is called after the request.
10
+ def initialize(app, handler:)
11
+ @app = app
12
+ @handler = handler
13
+ end
14
+
15
+ # @param input
16
+ # @param context
17
+ # @return [Output]
18
+ def call(input, context)
19
+ output = @app.call(input, context)
20
+ @handler.call(output, context)
21
+ output
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hearth
4
+ module Middleware
5
+ # A middleware that retries the request.
6
+ # @api private
7
+ class Retry
8
+ # @param [Class] app The next middleware in the stack.
9
+ # @param [Integer] max_attempts The maximum number of attempts to make
10
+ # before giving up.
11
+ # @param [Integer] max_delay The maximum delay between attempts.
12
+ def initialize(app, max_attempts:, max_delay:)
13
+ @app = app
14
+ @max_attempts = max_attempts
15
+ @max_delay = max_delay
16
+ end
17
+
18
+ # @param input
19
+ # @param context
20
+ # @return [Output]
21
+ def call(input, context)
22
+ attempt = 1
23
+ begin
24
+ @app.call(input, context)
25
+ rescue Hearth::HTTP::NetworkingError => e
26
+ raise e if attempt >= @max_attempts
27
+
28
+ Kernel.sleep(backoff_with_jitter(attempt))
29
+ attempt += 1
30
+ retry
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ # https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/
37
+ def backoff_with_jitter(attempt)
38
+ # scales like 1,2,4,8
39
+ Kernel.rand * [@max_delay, 2**(attempt - 1)].min
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hearth
4
+ module Middleware
5
+ # A middleware used to send the request.
6
+ # @api private
7
+ class Send
8
+ # @param [Class] _app The next middleware in the stack.
9
+ # @param [Boolean] stub_responses If true, a request is not sent and a
10
+ # stubbed response is returned.
11
+ # @param [Class] stub_class A stub object that is responsible for creating
12
+ # a stubbed response. It must respond to #stub and take the response
13
+ # and stub data as arguments.
14
+ # @param [Stubs] stubs A {Hearth::Stubbing:Stubs} object containing
15
+ # stubbed data for any given operation.
16
+ def initialize(_app, client:, stub_responses:, stub_class:, stubs:)
17
+ @client = client
18
+ @stub_responses = stub_responses
19
+ @stub_class = stub_class
20
+ @stubs = stubs
21
+ end
22
+
23
+ # @param _input
24
+ # @param context
25
+ # @return [Output]
26
+ def call(_input, context)
27
+ if @stub_responses
28
+ stub = @stubs.next(context.operation_name)
29
+ output = Output.new
30
+ apply_stub(stub, context, output)
31
+ output
32
+ else
33
+ @client.transmit(
34
+ request: context.request,
35
+ response: context.response
36
+ )
37
+ Output.new
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def apply_stub(stub, context, output)
44
+ case stub
45
+ when Proc
46
+ stub = stub.call(context)
47
+ apply_stub(stub, context, output) if stub
48
+ when Exception
49
+ output.error = stub
50
+ when Class
51
+ output.error = stub.new
52
+ when Hash
53
+ @stub_class.stub(context.response, stub: stub)
54
+ when NilClass
55
+ @stub_class.stub(context.response, stub: @stub_class.default)
56
+ else
57
+ raise ArgumentError, 'Unsupported stub type'
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hearth
4
+ module Middleware
5
+ # A middleware used to validate input.
6
+ # @api private
7
+ class Validate
8
+ # @param [Class] app The next middleware in the stack.
9
+ # @param [Boolean] validate_input If true, the input is validated against
10
+ # the model and an error is raised for unexpected types.
11
+ # @param [Class] validator A validator object responsible for validating
12
+ # the input. It must respond to #validate! and take input and a context
13
+ # as arguments.
14
+ def initialize(app, validate_input:, validator:)
15
+ @app = app
16
+ @validate_input = validate_input
17
+ @validator = validator
18
+ end
19
+
20
+ # @param input
21
+ # @param context
22
+ # @return [Output]
23
+ def call(input, context)
24
+ @validator.validate!(input, context: 'input') if @validate_input
25
+ @app.call(input, context)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'middleware/around_handler'
4
+ require_relative 'middleware/build'
5
+ require_relative 'middleware/host_prefix'
6
+ require_relative 'middleware/parse'
7
+ require_relative 'middleware/request_handler'
8
+ require_relative 'middleware/response_handler'
9
+ require_relative 'middleware/retry'
10
+ require_relative 'middleware/send'
11
+ require_relative 'middleware/validate'
12
+
13
+ module Hearth
14
+ # @api private
15
+ module Middleware; end
16
+ end
@@ -0,0 +1,246 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hearth
4
+ # A utility class for registering middleware for a request.
5
+ # You register middleware handlers to execute relative to
6
+ # Middleware classes. You can register middleware
7
+ # before/after/around any middleware class in the stack.
8
+ # You may also remove middleware from the stack.
9
+ # There are also convenience methods
10
+ # (eg: before_build, after_parse, ect)
11
+ # defined for all of the key request lifecycle events:
12
+ #
13
+ # * validate
14
+ # * host_prefix
15
+ # * build
16
+ # * send
17
+ # * parse
18
+ # * retry
19
+ #
20
+ # You can register request handlers that invoke before, after,
21
+ # or around each lifecycle events. These handlers are
22
+ # request, response, or around handlers.
23
+ #
24
+ # All before/after/around methods are defined on the class
25
+ # and instance and are chainable:
26
+ #
27
+ # MiddlewareBuilder.before_send(my_request_handler)
28
+ # .after_parse(my_response_handler)
29
+ #
30
+ # ## Request Handlers
31
+ #
32
+ # A request handler is invoked before the request is sent.
33
+ #
34
+ # # invoked after a request has been built, but before it has
35
+ # # been signed/authorized
36
+ # middleware.before_build do |input, context|
37
+ # # use input, inspect or modify the request
38
+ # # context.request
39
+ # end
40
+ #
41
+ # ## Response Handlers
42
+ #
43
+ # A response handler is invoked after the HTTP request has been sent
44
+ # and a HTTP response has been received.
45
+ #
46
+ # # invoked after the HTTP response has been parsed
47
+ # middleware.after_parse do |output, context|
48
+ # # inspect or modify the output or context
49
+ # # output.data
50
+ # # output.error
51
+ # # context.response
52
+ # end
53
+ #
54
+ # ## Around Handlers
55
+ #
56
+ # Around handlers see a request before it has been sent along
57
+ # with the response returned. Around handlers must invoke `#call`
58
+ # method of the next middleware in the stack. Around handlers
59
+ # must also return the response returned from the next middleware.
60
+ #
61
+ # # invoke before the request has been sent, receives the
62
+ # # response from the send middleware
63
+ # middleware.around_send do |app, input, context|
64
+ #
65
+ # # this code is invoked before the request is sent
66
+ # # ...
67
+ #
68
+ # # around handlers MUST call the next middleware in the stack
69
+ # output = app.call(input, context)
70
+ #
71
+ # # this code is invoked after the response has been received
72
+ # # ...
73
+ #
74
+ # # around handlers must return the response down the stack
75
+ # output
76
+ # end
77
+ #
78
+ # ## Removing Middleware
79
+ # You may remove existing middleware from the stack using either the class
80
+ # or instance `remove` methods and providing the middleware class to
81
+ # be removed. The remove methods are chainable and convenience methods are
82
+ # defined for the same set of lifecycle events as the `before`, `after`
83
+ # and `around` methods:
84
+ #
85
+ # # create a middleware builder that removes send and build middlewares
86
+ # MiddlewareBuilder
87
+ # .remove_send
88
+ # .remove_build
89
+ #
90
+ class MiddlewareBuilder
91
+ # @private
92
+ BOTH = 'expected a handler or a Proc, got both'
93
+
94
+ # @private
95
+ NEITHER = 'expected a handler or a Proc, got neither'
96
+
97
+ # @private
98
+ TOO_MANY = 'wrong number of arguments (given %<count>d, expected 0 or 1)'
99
+
100
+ # @private
101
+ CALLABLE = 'expected handler to respond to #call'
102
+
103
+ # @param [Proc, MiddlewareBuilder] middleware
104
+ #
105
+ # If `middleware` is another {MiddlewareBuilder} instance, then
106
+ # the middleware handlers are copied.
107
+ #
108
+ def initialize(middleware = nil)
109
+ @middleware = []
110
+ case middleware
111
+ when MiddlewareBuilder then @middleware.concat(middleware.to_a)
112
+ when nil then nil
113
+ else
114
+ raise ArgumentError, 'expected :middleware to be a' \
115
+ 'Hearth::MiddlewareBuilder,' \
116
+ " got #{middleware.class}"
117
+ end
118
+ end
119
+
120
+ # @param [MiddlewareStack] middleware_stack
121
+ def apply(middleware_stack)
122
+ @middleware.each do |handler|
123
+ method, relation, middleware, kwargs = handler
124
+ if method == :remove
125
+ middleware_stack.remove(relation)
126
+ else
127
+ middleware_stack.send(method, relation, middleware, **kwargs)
128
+ end
129
+ end
130
+ end
131
+
132
+ def before(klass, *args, &block)
133
+ @middleware << [
134
+ :use_before,
135
+ klass,
136
+ Middleware::RequestHandler,
137
+ { handler: handler_or_proc!(args, &block) }
138
+ ]
139
+ self
140
+ end
141
+
142
+ def after(klass, *args, &block)
143
+ @middleware << [
144
+ :use_before,
145
+ klass,
146
+ Middleware::ResponseHandler,
147
+ { handler: handler_or_proc!(args, &block) }
148
+ ]
149
+ self
150
+ end
151
+
152
+ def around(klass, *args, &block)
153
+ @middleware << [
154
+ :use_before,
155
+ klass,
156
+ Middleware::AroundHandler,
157
+ { handler: handler_or_proc!(args, &block) }
158
+ ]
159
+ self
160
+ end
161
+
162
+ def remove(klass)
163
+ @middleware << [
164
+ :remove,
165
+ klass,
166
+ nil,
167
+ nil
168
+ ]
169
+ self
170
+ end
171
+
172
+ # Define convenience methods for chaining
173
+ class << self
174
+ def before(klass, *args, &block)
175
+ MiddlewareBuilder.new.before(klass, *args, &block)
176
+ end
177
+
178
+ def after(klass, *args, &block)
179
+ MiddlewareBuilder.new.after(klass, *args, &block)
180
+ end
181
+
182
+ def around(klass, *args, &block)
183
+ MiddlewareBuilder.new.around(klass, *args, &block)
184
+ end
185
+
186
+ def remove(klass)
187
+ MiddlewareBuilder.new.remove(klass)
188
+ end
189
+ end
190
+
191
+ # define convenience methods for standard middleware classes
192
+ # these define methods and class methods for before,after,around
193
+ # eg: before_build, after_build, around_build.
194
+ STANDARD_MIDDLEWARE = [
195
+ Hearth::Middleware::Validate,
196
+ Hearth::Middleware::HostPrefix,
197
+ Hearth::Middleware::Build,
198
+ Hearth::Middleware::Send,
199
+ Hearth::Middleware::Retry,
200
+ Hearth::Middleware::Parse
201
+ ].freeze
202
+
203
+ STANDARD_MIDDLEWARE.each do |klass|
204
+ simple_step_name = klass.to_s.split('::').last.downcase
205
+ %w[before after around].each do |method|
206
+ method_name = "#{method}_#{simple_step_name}"
207
+ define_method(method_name) do |*args, &block|
208
+ return send(method, klass, *args, &block)
209
+ end
210
+
211
+ define_singleton_method(method_name) do |*args, &block|
212
+ return send(method, klass, *args, &block)
213
+ end
214
+ end
215
+
216
+ remove_method_name = "remove_#{simple_step_name}"
217
+ define_method(remove_method_name) do
218
+ return remove(klass)
219
+ end
220
+
221
+ define_singleton_method(remove_method_name) do
222
+ return remove(klass)
223
+ end
224
+ end
225
+
226
+ def to_a
227
+ @middleware
228
+ end
229
+
230
+ private
231
+
232
+ def handler_or_proc!(args, &block)
233
+ validate_args!(args, &block)
234
+ callable = args.first || Proc.new(&block)
235
+ raise ArgumentError, CALLABLE unless callable.respond_to?(:call)
236
+
237
+ callable
238
+ end
239
+
240
+ def validate_args!(args, &block)
241
+ raise ArgumentError, BOTH if args.size.positive? && block
242
+ raise ArgumentError, NEITHER if args.empty? && block.nil?
243
+ raise ArgumentError, format(TOO_MANY, count: args.size) if args.size > 1
244
+ end
245
+ end
246
+ end