hearth 1.0.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
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