seahorse 0.1.0 → 1.0.0.pre1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGELOG.md +12 -0
- data/VERSION +1 -0
- data/lib/seahorse/api_error.rb +15 -0
- data/lib/seahorse/block_io.rb +24 -0
- data/lib/seahorse/context.rb +34 -0
- data/lib/seahorse/http/api_error.rb +29 -0
- data/lib/seahorse/http/client.rb +152 -0
- data/lib/seahorse/http/error_parser.rb +105 -0
- data/lib/seahorse/http/headers.rb +70 -0
- data/lib/seahorse/http/middleware/content_length.rb +28 -0
- data/lib/seahorse/http/networking_error.rb +20 -0
- data/lib/seahorse/http/request.rb +132 -0
- data/lib/seahorse/http/response.rb +29 -0
- data/lib/seahorse/http.rb +36 -0
- data/lib/seahorse/json/parse_error.rb +18 -0
- data/lib/seahorse/json.rb +30 -0
- data/lib/seahorse/middleware/around_handler.rb +24 -0
- data/lib/seahorse/middleware/build.rb +26 -0
- data/lib/seahorse/middleware/host_prefix.rb +48 -0
- data/lib/seahorse/middleware/parse.rb +42 -0
- data/lib/seahorse/middleware/request_handler.rb +24 -0
- data/lib/seahorse/middleware/response_handler.rb +25 -0
- data/lib/seahorse/middleware/retry.rb +43 -0
- data/lib/seahorse/middleware/send.rb +62 -0
- data/lib/seahorse/middleware/validate.rb +29 -0
- data/lib/seahorse/middleware.rb +16 -0
- data/lib/seahorse/middleware_builder.rb +246 -0
- data/lib/seahorse/middleware_stack.rb +73 -0
- data/lib/seahorse/output.rb +20 -0
- data/lib/seahorse/structure.rb +40 -0
- data/lib/seahorse/stubbing/client_stubs.rb +115 -0
- data/lib/seahorse/stubbing/stubs.rb +32 -0
- data/lib/seahorse/time_helper.rb +35 -0
- data/lib/seahorse/union.rb +10 -0
- data/lib/seahorse/validator.rb +20 -0
- data/lib/seahorse/waiters/errors.rb +15 -0
- data/lib/seahorse/waiters/poller.rb +132 -0
- data/lib/seahorse/waiters/waiter.rb +79 -0
- data/lib/seahorse/xml/formatter.rb +68 -0
- data/lib/seahorse/xml/node.rb +123 -0
- data/lib/seahorse/xml/parse_error.rb +18 -0
- data/lib/seahorse/xml.rb +58 -0
- data/lib/seahorse.rb +23 -13
- 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 +73 -54
- data/LICENSE +0 -12
- data/README.md +0 -3
- data/Rakefile +0 -7
- data/lib/seahorse/api_translator/inflector.rb +0 -37
- data/lib/seahorse/api_translator/operation.rb +0 -150
- data/lib/seahorse/api_translator/shape.rb +0 -235
- data/lib/seahorse/controller.rb +0 -87
- data/lib/seahorse/model.rb +0 -82
- data/lib/seahorse/operation.rb +0 -66
- data/lib/seahorse/param_validator.rb +0 -158
- data/lib/seahorse/railtie.rb +0 -7
- data/lib/seahorse/router.rb +0 -20
- data/lib/seahorse/shape_builder.rb +0 -84
- data/lib/seahorse/type.rb +0 -220
- data/lib/seahorse/version.rb +0 -3
- data/lib/tasks/seahorse_tasks.rake +0 -24
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Seahorse
|
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,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Seahorse
|
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 Seahorse
|
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 Seahorse
|
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 Seahorse
|
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 Seahorse
|
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:%S.%LZ')
|
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
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Seahorse
|
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 Seahorse
|
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 Seahorse
|
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 Seahorse::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 Seahorse
|
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 Seahorse
|
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
|