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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +12 -0
- data/VERSION +1 -0
- data/lib/hearth/api_error.rb +15 -0
- data/lib/hearth/block_io.rb +24 -0
- data/lib/hearth/context.rb +34 -0
- data/lib/hearth/http/api_error.rb +29 -0
- data/lib/hearth/http/client.rb +152 -0
- data/lib/hearth/http/error_parser.rb +105 -0
- data/lib/hearth/http/headers.rb +70 -0
- data/lib/hearth/http/middleware/content_length.rb +29 -0
- data/lib/hearth/http/networking_error.rb +20 -0
- data/lib/hearth/http/request.rb +132 -0
- data/lib/hearth/http/response.rb +29 -0
- data/lib/hearth/http.rb +36 -0
- data/lib/hearth/json/parse_error.rb +18 -0
- data/lib/hearth/json.rb +30 -0
- data/lib/hearth/middleware/around_handler.rb +24 -0
- data/lib/hearth/middleware/build.rb +26 -0
- data/lib/hearth/middleware/host_prefix.rb +48 -0
- data/lib/hearth/middleware/parse.rb +42 -0
- data/lib/hearth/middleware/request_handler.rb +24 -0
- data/lib/hearth/middleware/response_handler.rb +25 -0
- data/lib/hearth/middleware/retry.rb +43 -0
- data/lib/hearth/middleware/send.rb +62 -0
- data/lib/hearth/middleware/validate.rb +29 -0
- data/lib/hearth/middleware.rb +16 -0
- data/lib/hearth/middleware_builder.rb +246 -0
- data/lib/hearth/middleware_stack.rb +73 -0
- data/lib/hearth/number_helper.rb +33 -0
- data/lib/hearth/output.rb +20 -0
- data/lib/hearth/structure.rb +40 -0
- data/lib/hearth/stubbing/client_stubs.rb +115 -0
- data/lib/hearth/stubbing/stubs.rb +32 -0
- data/lib/hearth/time_helper.rb +35 -0
- data/lib/hearth/union.rb +10 -0
- data/lib/hearth/validator.rb +20 -0
- data/lib/hearth/waiters/errors.rb +15 -0
- data/lib/hearth/waiters/poller.rb +132 -0
- data/lib/hearth/waiters/waiter.rb +79 -0
- data/lib/hearth/xml/formatter.rb +68 -0
- data/lib/hearth/xml/node.rb +123 -0
- data/lib/hearth/xml/node_matcher.rb +24 -0
- data/lib/hearth/xml/parse_error.rb +18 -0
- data/lib/hearth/xml.rb +58 -0
- data/lib/hearth.rb +26 -0
- data/sig/lib/seahorse/api_error.rbs +10 -0
- data/sig/lib/seahorse/document.rbs +2 -0
- data/sig/lib/seahorse/http/api_error.rbs +21 -0
- data/sig/lib/seahorse/http/headers.rbs +47 -0
- data/sig/lib/seahorse/http/response.rbs +21 -0
- data/sig/lib/seahorse/simple_delegator.rbs +3 -0
- data/sig/lib/seahorse/structure.rbs +18 -0
- data/sig/lib/seahorse/stubbing/client_stubs.rbs +103 -0
- data/sig/lib/seahorse/stubbing/stubs.rbs +14 -0
- data/sig/lib/seahorse/union.rbs +6 -0
- metadata +111 -0
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hearth
|
4
|
+
# @api private
|
5
|
+
class MiddlewareStack
|
6
|
+
def initialize
|
7
|
+
@middleware = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def use(middleware, **middleware_kwargs)
|
11
|
+
@middleware.push([middleware, middleware_kwargs])
|
12
|
+
end
|
13
|
+
|
14
|
+
def use_before(before, middleware, **middleware_kwargs)
|
15
|
+
new_middleware = []
|
16
|
+
@middleware.each do |klass, args|
|
17
|
+
new_middleware << [middleware, middleware_kwargs] if before == klass
|
18
|
+
new_middleware << [klass, args]
|
19
|
+
end
|
20
|
+
unless new_middleware.size == @middleware.size + 1
|
21
|
+
raise ArgumentError,
|
22
|
+
"Failed to insert #{middleware} before #{before}"
|
23
|
+
end
|
24
|
+
|
25
|
+
@middleware = new_middleware
|
26
|
+
end
|
27
|
+
|
28
|
+
def use_after(after, middleware, **middleware_kwargs)
|
29
|
+
new_middleware = []
|
30
|
+
@middleware.each do |klass, args|
|
31
|
+
new_middleware << [klass, args]
|
32
|
+
new_middleware << [middleware, middleware_kwargs] if after == klass
|
33
|
+
end
|
34
|
+
unless new_middleware.size == @middleware.size + 1
|
35
|
+
raise ArgumentError,
|
36
|
+
"Failed to insert #{middleware} after #{after}"
|
37
|
+
end
|
38
|
+
|
39
|
+
@middleware = new_middleware
|
40
|
+
end
|
41
|
+
|
42
|
+
def remove(remove)
|
43
|
+
new_middleware = []
|
44
|
+
@middleware.each do |klass, args|
|
45
|
+
new_middleware << [klass, args] unless klass == remove
|
46
|
+
end
|
47
|
+
|
48
|
+
unless new_middleware.size == @middleware.size - 1
|
49
|
+
raise ArgumentError,
|
50
|
+
"Failed to remove #{remove}"
|
51
|
+
end
|
52
|
+
|
53
|
+
@middleware = new_middleware
|
54
|
+
end
|
55
|
+
|
56
|
+
# @param input
|
57
|
+
# @param context
|
58
|
+
# @return [Output]
|
59
|
+
def run(input:, context:)
|
60
|
+
stack.call(input, context)
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def stack
|
66
|
+
app = nil
|
67
|
+
@middleware.reverse_each do |(middleware, middleware_args)|
|
68
|
+
app = middleware.new(app, **middleware_args)
|
69
|
+
end
|
70
|
+
app
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hearth
|
4
|
+
# A module that provides helper methods to convert Numeric objects to
|
5
|
+
# protocol specific serializable formats.
|
6
|
+
# @api private
|
7
|
+
module NumberHelper
|
8
|
+
class << self
|
9
|
+
# @param [Number,String] input
|
10
|
+
# @return [String] The serialized number
|
11
|
+
def serialize(input)
|
12
|
+
if input == ::Float::INFINITY then 'Infinity'
|
13
|
+
elsif input == -::Float::INFINITY then '-Infinity'
|
14
|
+
elsif input&.nan? then 'NaN'
|
15
|
+
else
|
16
|
+
input
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# @param [String] str
|
21
|
+
# @return [Number] The input as a number
|
22
|
+
def deserialize(str)
|
23
|
+
case str
|
24
|
+
when 'Infinity' then ::Float::INFINITY
|
25
|
+
when '-Infinity' then -::Float::INFINITY
|
26
|
+
when 'NaN' then ::Float::NAN
|
27
|
+
when nil then nil
|
28
|
+
else str.to_f
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hearth
|
4
|
+
# A wrapper class that contains an error or data from the response.
|
5
|
+
# @api private
|
6
|
+
class Output
|
7
|
+
# @param [StandardError] error The error class to be raised.
|
8
|
+
# @param [Struct] data The data returned by a client.
|
9
|
+
def initialize(error: nil, data: nil)
|
10
|
+
@error = error
|
11
|
+
@data = data
|
12
|
+
end
|
13
|
+
|
14
|
+
# @return [StandardError, nil]
|
15
|
+
attr_accessor :error
|
16
|
+
|
17
|
+
# @return [Struct, nil]
|
18
|
+
attr_accessor :data
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hearth
|
4
|
+
# A module mixed into Structs that provides utility methods.
|
5
|
+
module Structure
|
6
|
+
# Deeply converts the Struct into a hash. Structure members that
|
7
|
+
# are `nil` are omitted from the resultant hash.
|
8
|
+
#
|
9
|
+
# @return [Hash]
|
10
|
+
def to_h(obj = self)
|
11
|
+
case obj
|
12
|
+
when Struct
|
13
|
+
_to_h_struct(obj)
|
14
|
+
when Hash
|
15
|
+
_to_h_hash(obj)
|
16
|
+
when Array, Set
|
17
|
+
obj.collect { |value| to_hash(value) }
|
18
|
+
when Union
|
19
|
+
obj.to_h
|
20
|
+
else
|
21
|
+
obj
|
22
|
+
end
|
23
|
+
end
|
24
|
+
alias to_hash to_h
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def _to_h_struct(obj)
|
29
|
+
obj.each_pair.with_object({}) do |(member, value), hash|
|
30
|
+
hash[member] = to_hash(value) unless value.nil?
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def _to_h_hash(obj)
|
35
|
+
obj.each.with_object({}) do |(key, value), hash|
|
36
|
+
hash[key] = to_hash(value)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'stubs'
|
4
|
+
|
5
|
+
module Hearth
|
6
|
+
# This module provides the ability to specify the data and/or errors to
|
7
|
+
# return when a client is using stubbed responses.
|
8
|
+
# This module should be included in generated service clients.
|
9
|
+
#
|
10
|
+
# Pass `stub_responses: true` to a client constructor to enable this
|
11
|
+
# behavior.
|
12
|
+
module ClientStubs
|
13
|
+
# Configures what data / errors should be returned from the named operation
|
14
|
+
# when response stubbing is enabled.
|
15
|
+
#
|
16
|
+
# ## Basic usage
|
17
|
+
#
|
18
|
+
# When you enable response stubbing, the client will generate fake
|
19
|
+
# responses and will not make any HTTP requests.
|
20
|
+
#
|
21
|
+
# client = Service::Client.new(stub_responses: true)
|
22
|
+
# client.operation
|
23
|
+
# #=> #<struct Service:Types::Operation param1=[], param2=nil>
|
24
|
+
#
|
25
|
+
# You can specify the stub data using {#stub_responses}
|
26
|
+
#
|
27
|
+
# client = Service::Client.new(stub_responses: true)
|
28
|
+
# client.stub_responses(:operation, {
|
29
|
+
# param1: [{ name: 'value1' }]
|
30
|
+
# })
|
31
|
+
#
|
32
|
+
# client.operation.param1.map(&:name)
|
33
|
+
# #=> ['value1']
|
34
|
+
#
|
35
|
+
# ## Stubbing Errors
|
36
|
+
#
|
37
|
+
# When stubbing is enabled, the SDK will default to generate
|
38
|
+
# fake responses with placeholder values. You can override the data
|
39
|
+
# returned. You can also specify errors it should raise.
|
40
|
+
#
|
41
|
+
# # to simulate errors, give the error class, you must
|
42
|
+
# # be able to construct an instance with `.new`
|
43
|
+
# client.stub_responses(:operation, Timeout::Error)
|
44
|
+
# client.operation(param1: 'value')
|
45
|
+
# #=> raises new Timeout::Error
|
46
|
+
#
|
47
|
+
# # or you can give an instance of an error class
|
48
|
+
# client.stub_responses(:operation, RuntimeError.new('custom message'))
|
49
|
+
# client.operation(param1: 'value')
|
50
|
+
# #=> raises the given runtime error object
|
51
|
+
#
|
52
|
+
# ## Dynamic Stubbing
|
53
|
+
#
|
54
|
+
# In addition to creating static stubs, it's also possible to generate
|
55
|
+
# stubs dynamically based on the parameters with which operations were
|
56
|
+
# called, by passing a `Proc` object:
|
57
|
+
#
|
58
|
+
# client.stub_responses(:operation, -> (context) {
|
59
|
+
# if context.params[:param] == 'foo'
|
60
|
+
# # return a stub
|
61
|
+
# { param1: [{ name: 'value1'}]}
|
62
|
+
# else
|
63
|
+
# # return an error
|
64
|
+
# Services::Errors::NotFound
|
65
|
+
# end
|
66
|
+
# })
|
67
|
+
#
|
68
|
+
# ## Stubbing Raw Protocol Responses
|
69
|
+
#
|
70
|
+
# As an alternative to providing the response data, you can modify the
|
71
|
+
# response object provided by the `Proc` object and then
|
72
|
+
# return nil.
|
73
|
+
#
|
74
|
+
# client.stub_responses(:operation, -> (context) {
|
75
|
+
# context.response.status = 404 # simulate an error
|
76
|
+
# nil
|
77
|
+
# })
|
78
|
+
#
|
79
|
+
# ## Stubbing Multiple Responses
|
80
|
+
#
|
81
|
+
# Calling an operation multiple times will return similar responses.
|
82
|
+
# You can configure multiple stubs and they will be returned in sequence.
|
83
|
+
#
|
84
|
+
# client.stub_responses(:operation, [
|
85
|
+
# Errors::NotFound,
|
86
|
+
# { content_length: 150 },
|
87
|
+
# ])
|
88
|
+
#
|
89
|
+
# client.operation(param1: 'value1')
|
90
|
+
# #=> raises Errors::NotFound
|
91
|
+
#
|
92
|
+
# resp = client.operation(param1: 'value2')
|
93
|
+
# resp.content_length #=> 150
|
94
|
+
#
|
95
|
+
# @param [Symbol] operation_name
|
96
|
+
#
|
97
|
+
# @param [Mixed] stubs One or more responses to return from the named
|
98
|
+
# operation.
|
99
|
+
#
|
100
|
+
# @return [void]
|
101
|
+
#
|
102
|
+
# @raise [RuntimeError] Raises a runtime error when called
|
103
|
+
# on a client that has not enabled response stubbing via
|
104
|
+
# `:stub_responses => true`.
|
105
|
+
def stub_responses(operation_name, *stubs)
|
106
|
+
if @stub_responses
|
107
|
+
@stubs.add_stubs(operation_name, stubs.flatten)
|
108
|
+
else
|
109
|
+
msg = 'Stubbing is not enabled. Enable stubbing in the constructor '\
|
110
|
+
'with `stub_responses: true`'
|
111
|
+
raise ArgumentError, msg
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hearth
|
4
|
+
# @api private
|
5
|
+
module Stubbing
|
6
|
+
# Provides a thread safe data structure for adding and getting stubs
|
7
|
+
# per operation.
|
8
|
+
class Stubs
|
9
|
+
def initialize
|
10
|
+
@stubs = {}
|
11
|
+
@stub_mutex = Mutex.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def add_stubs(operation_name, stubs)
|
15
|
+
@stub_mutex.synchronize do
|
16
|
+
@stubs[operation_name.to_sym] = stubs
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def next(operation_name)
|
21
|
+
@stub_mutex.synchronize do
|
22
|
+
stubs = @stubs[operation_name] || []
|
23
|
+
case stubs.length
|
24
|
+
when 0 then nil
|
25
|
+
when 1 then stubs.first
|
26
|
+
else stubs.shift
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'time'
|
4
|
+
|
5
|
+
module Hearth
|
6
|
+
# A module that provides helper methods to convert from Time objects to
|
7
|
+
# protocol specific serializable formats.
|
8
|
+
# @api private
|
9
|
+
module TimeHelper
|
10
|
+
class << self
|
11
|
+
# @param [Time] time
|
12
|
+
# @return [String<Date Time>] The time as an ISO8601 string.
|
13
|
+
def to_date_time(time)
|
14
|
+
time.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
|
15
|
+
end
|
16
|
+
|
17
|
+
# @param [Time] time
|
18
|
+
# @return [Float<Epoch Seconds>] Returns float value of
|
19
|
+
# epoch seconds with millisecond precision.
|
20
|
+
def to_epoch_seconds(time)
|
21
|
+
time = time.utc
|
22
|
+
epoch_seconds = time.to_i
|
23
|
+
epoch_seconds += (time.nsec / 1_000_000) / 1000.0
|
24
|
+
epoch_seconds
|
25
|
+
end
|
26
|
+
|
27
|
+
# @param [Time] time
|
28
|
+
# @return [String<Http Date>] Returns the time formatted
|
29
|
+
# as an HTTP header date.
|
30
|
+
def to_http_date(time)
|
31
|
+
time.utc.httpdate
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/hearth/union.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hearth
|
4
|
+
# Utility module for working with request parameters.
|
5
|
+
#
|
6
|
+
# * Validate structure of parameters against the expected type.
|
7
|
+
# * Raise errors with context when validation fails.
|
8
|
+
# @api private
|
9
|
+
module Validator
|
10
|
+
# Validate the given values is of the given type(s).
|
11
|
+
# @raise [ArgumentError] Raises when the value is not one of given type(s).
|
12
|
+
def self.validate!(value, *types, context:)
|
13
|
+
return if !value || types.any? { |type| value.is_a?(type) }
|
14
|
+
|
15
|
+
raise ArgumentError,
|
16
|
+
"Expected #{context} to be in "\
|
17
|
+
"[#{types.map(&:to_s).join(', ')}], got #{value.class}."
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hearth
|
4
|
+
module Waiters
|
5
|
+
module Errors
|
6
|
+
class WaiterFailed < StandardError; end
|
7
|
+
|
8
|
+
class FailureStateError < StandardError; end
|
9
|
+
|
10
|
+
class UnexpectedError < StandardError; end
|
11
|
+
|
12
|
+
class MaxWaitTimeExceeded < StandardError; end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'jmespath'
|
4
|
+
|
5
|
+
module Hearth
|
6
|
+
module Waiters
|
7
|
+
# Abstract Poller used by generated service Waiters. This class handles
|
8
|
+
# sending the request and matching input or output.
|
9
|
+
class Poller
|
10
|
+
# @api private
|
11
|
+
def initialize(options = {})
|
12
|
+
@operation_name = options[:operation_name]
|
13
|
+
@acceptors = options[:acceptors]
|
14
|
+
@input = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
# Makes an API call, returning the resultant state and the response.
|
18
|
+
#
|
19
|
+
# * `:success` - A success state has been matched.
|
20
|
+
# * `:failure` - A terminate failure state has been matched.
|
21
|
+
# * `:retry` - The waiter may be retried.
|
22
|
+
# * `:error` - The waiter encountered an un-expected error.
|
23
|
+
#
|
24
|
+
# @example A trival (bad) example of a waiter that polls indefinetly.
|
25
|
+
#
|
26
|
+
# loop do
|
27
|
+
#
|
28
|
+
# state, resp = poller.call(client, params, options)
|
29
|
+
#
|
30
|
+
# case state
|
31
|
+
# when :success then return true
|
32
|
+
# when :failure then return false
|
33
|
+
# when :retry then next
|
34
|
+
# when :error then raise 'oops'
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# @param [Client] client
|
40
|
+
# @param [Hash] params
|
41
|
+
# @param [Hash] options
|
42
|
+
# @return [Array<Symbol,Response>]
|
43
|
+
def call(client, params = {}, options = {})
|
44
|
+
begin
|
45
|
+
options = options.merge(input_output_middleware)
|
46
|
+
response = client.send(@operation_name, params, options)
|
47
|
+
rescue Hearth::ApiError => e
|
48
|
+
error = e
|
49
|
+
end
|
50
|
+
resp_or_error = error || response
|
51
|
+
@acceptors.each do |acceptor|
|
52
|
+
if acceptor_matches?(acceptor[:matcher], response, error)
|
53
|
+
return [acceptor[:state].to_sym, resp_or_error]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
[error ? :error : :retry, resp_or_error]
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def input_output_middleware
|
62
|
+
middleware = lambda do |input, _context|
|
63
|
+
@input = input # get internal details of middleware
|
64
|
+
end
|
65
|
+
{ middleware: MiddlewareBuilder.before_send(middleware) }
|
66
|
+
end
|
67
|
+
|
68
|
+
def acceptor_matches?(matcher, response, error)
|
69
|
+
if (m = matcher[:success])
|
70
|
+
success_matcher?(m, response, error)
|
71
|
+
elsif (m = matcher[:errorType])
|
72
|
+
error_type_matcher?(m, error)
|
73
|
+
elsif (m = matcher[:inputOutput])
|
74
|
+
input_output_matcher?(m, response, error)
|
75
|
+
elsif (m = matcher[:output])
|
76
|
+
output_matcher?(m, response, error)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def success_matcher?(matcher, response, error)
|
81
|
+
(matcher == true && response) || (matcher == false && error)
|
82
|
+
end
|
83
|
+
|
84
|
+
def error_type_matcher?(matcher, error)
|
85
|
+
# handle shape ID cases
|
86
|
+
matcher = matcher.split('#').last.split('$').first
|
87
|
+
error.class.to_s.include?(matcher) || error.error_code == matcher
|
88
|
+
end
|
89
|
+
|
90
|
+
def input_output_matcher?(matcher, response, error)
|
91
|
+
return false if error
|
92
|
+
|
93
|
+
data = { input: @input, output: response }
|
94
|
+
send(
|
95
|
+
"matches_#{matcher[:comparator]}?",
|
96
|
+
JMESPath.search(matcher[:path], data),
|
97
|
+
matcher[:expected]
|
98
|
+
)
|
99
|
+
end
|
100
|
+
|
101
|
+
def output_matcher?(matcher, response, error)
|
102
|
+
return false if error
|
103
|
+
|
104
|
+
send(
|
105
|
+
"matches_#{matcher[:comparator]}?",
|
106
|
+
JMESPath.search(matcher[:path], response),
|
107
|
+
matcher[:expected]
|
108
|
+
)
|
109
|
+
end
|
110
|
+
|
111
|
+
# rubocop:disable Naming/MethodName
|
112
|
+
def matches_stringEquals?(value, expected)
|
113
|
+
value == expected
|
114
|
+
end
|
115
|
+
|
116
|
+
def matches_booleanEquals?(value, expected)
|
117
|
+
value.to_s == expected
|
118
|
+
end
|
119
|
+
|
120
|
+
def matches_allStringEquals?(values, expected)
|
121
|
+
values.is_a?(Array) && !values.empty? &&
|
122
|
+
values.all? { |v| v == expected }
|
123
|
+
end
|
124
|
+
|
125
|
+
def matches_anyStringEquals?(values, expected)
|
126
|
+
values.is_a?(Array) && !values.empty? &&
|
127
|
+
values.any? { |v| v == expected }
|
128
|
+
end
|
129
|
+
# rubocop:enable Naming/MethodName
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hearth
|
4
|
+
module Waiters
|
5
|
+
# Abstract waiter class with high level logic for polling and waiting.
|
6
|
+
class Waiter
|
7
|
+
# @api private
|
8
|
+
def initialize(options = {})
|
9
|
+
unless options[:max_wait_time].is_a?(Integer)
|
10
|
+
raise ArgumentError,
|
11
|
+
'Waiter must be initialized with `:max_wait_time`'
|
12
|
+
end
|
13
|
+
|
14
|
+
@max_wait_time = options[:max_wait_time]
|
15
|
+
@min_delay = options[:min_delay]
|
16
|
+
@max_delay = options[:max_delay]
|
17
|
+
@poller = options[:poller]
|
18
|
+
|
19
|
+
@remaining_time = @max_wait_time
|
20
|
+
@one_more_retry = false
|
21
|
+
end
|
22
|
+
|
23
|
+
attr_reader :max_wait_time, :min_delay, :max_delay
|
24
|
+
|
25
|
+
# @param [Client] client The client to poll with.
|
26
|
+
# @param [Hash] params The params for the operation.
|
27
|
+
# @param [Hash] options Any operation options.
|
28
|
+
def wait(client, params = {}, options = {})
|
29
|
+
poll(client, params, options)
|
30
|
+
true
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
# https://awslabs.github.io/smithy/1.0/spec/waiters.html#waiter-workflow
|
36
|
+
def poll(client, params, options)
|
37
|
+
n = 0
|
38
|
+
loop do
|
39
|
+
state, resp_or_error = @poller.call(client, params, options)
|
40
|
+
n += 1
|
41
|
+
|
42
|
+
case state
|
43
|
+
when :retry then nil
|
44
|
+
when :success then return
|
45
|
+
when :failure then raise Errors::FailureStateError, resp_or_error
|
46
|
+
when :error then raise Errors::UnexpectedError, resp_or_error
|
47
|
+
end
|
48
|
+
|
49
|
+
raise Errors::MaxWaitTimeExceeded if @one_more_retry
|
50
|
+
|
51
|
+
delay = delay(n)
|
52
|
+
@remaining_time -= delay
|
53
|
+
Kernel.sleep(delay)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def delay(attempt)
|
58
|
+
delay = if attempt > attempt_ceiling
|
59
|
+
max_delay
|
60
|
+
else
|
61
|
+
min_delay * (2**(attempt - 1))
|
62
|
+
end
|
63
|
+
|
64
|
+
delay = Kernel.rand(min_delay..delay)
|
65
|
+
|
66
|
+
if @remaining_time - delay <= min_delay
|
67
|
+
delay = @remaining_time - min_delay
|
68
|
+
@one_more_retry = true
|
69
|
+
end
|
70
|
+
|
71
|
+
delay
|
72
|
+
end
|
73
|
+
|
74
|
+
def attempt_ceiling
|
75
|
+
(Math.log(max_delay.to_f / min_delay) / Math.log(2)) + 1
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'stringio'
|
4
|
+
|
5
|
+
module Hearth
|
6
|
+
module XML
|
7
|
+
# A class used for formatting XML strings.
|
8
|
+
# @api private
|
9
|
+
class Formatter
|
10
|
+
NEGATIVE_INDENT = 'indent must be greater than or equal to zero'
|
11
|
+
|
12
|
+
# @param [String] indent
|
13
|
+
# When `indent` is non-empty whitespace, then the XML will
|
14
|
+
# be pretty-formatted with each level of nodes indented by
|
15
|
+
# `indent`.
|
16
|
+
# @raise [ArgumentError] when indent is not a String.
|
17
|
+
def initialize(indent: '')
|
18
|
+
unless indent.is_a?(String)
|
19
|
+
raise ArgumentError, "expected a String, got #{indent.class}"
|
20
|
+
end
|
21
|
+
|
22
|
+
@indent = indent
|
23
|
+
@eol = indent.empty? ? '' : "\n"
|
24
|
+
end
|
25
|
+
|
26
|
+
# @param [Node] node
|
27
|
+
# @return [String<XML>]
|
28
|
+
def format(node)
|
29
|
+
buffer = StringIO.new
|
30
|
+
serialize(buffer, node, '')
|
31
|
+
buffer.string
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def serialize(buffer, node, pad)
|
37
|
+
return buffer.write(self_close_node(node, pad)) if node.empty?
|
38
|
+
return buffer.write(text_node(node, pad)) if node.text
|
39
|
+
|
40
|
+
serialize_nested(buffer, node, pad)
|
41
|
+
end
|
42
|
+
|
43
|
+
def self_close_node(node, pad)
|
44
|
+
"#{pad}<#{node.name}#{attrs(node)}/>#{@eol}"
|
45
|
+
end
|
46
|
+
|
47
|
+
def text_node(node, pad)
|
48
|
+
text = node.text.encode(xml: :text)
|
49
|
+
"#{pad}<#{node.name}#{attrs(node)}>#{text}</#{node.name}>#{@eol}"
|
50
|
+
end
|
51
|
+
|
52
|
+
def serialize_nested(buffer, node, pad)
|
53
|
+
buffer.write("#{pad}<#{node.name}#{attrs(node)}>#{@eol}")
|
54
|
+
nested_pad = "#{pad}#{@indent}"
|
55
|
+
node.child_nodes.each do |child_node|
|
56
|
+
serialize(buffer, child_node, nested_pad)
|
57
|
+
end
|
58
|
+
buffer.write("#{pad}</#{node.name}>#{@eol}")
|
59
|
+
end
|
60
|
+
|
61
|
+
def attrs(node)
|
62
|
+
node.attributes.map do |key, value|
|
63
|
+
" #{key}=#{value.to_s.encode(xml: :attr)}"
|
64
|
+
end.join
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|