smithy-client 1.0.0.pre0 → 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 +4 -4
- data/CHANGELOG.md +2 -0
- data/VERSION +1 -1
- data/lib/smithy-client/anonymous_provider.rb +12 -0
- data/lib/smithy-client/auth_option.rb +23 -0
- data/lib/smithy-client/auth_scheme.rb +25 -0
- data/lib/smithy-client/auth_schemes/anonymous.rb +18 -0
- data/lib/smithy-client/auth_schemes/http_api_key.rb +18 -0
- data/lib/smithy-client/auth_schemes/http_basic.rb +18 -0
- data/lib/smithy-client/auth_schemes/http_bearer.rb +18 -0
- data/lib/smithy-client/auth_schemes/http_digest.rb +18 -0
- data/lib/smithy-client/base.rb +200 -0
- data/lib/smithy-client/block_io.rb +36 -0
- data/lib/smithy-client/configuration.rb +222 -0
- data/lib/smithy-client/default_params.rb +91 -0
- data/lib/smithy-client/dynamic_errors.rb +82 -0
- data/lib/smithy-client/endpoint_rules.rb +186 -0
- data/lib/smithy-client/handler.rb +29 -0
- data/lib/smithy-client/handler_builder.rb +33 -0
- data/lib/smithy-client/handler_context.rb +67 -0
- data/lib/smithy-client/handler_list.rb +197 -0
- data/lib/smithy-client/handler_list_entry.rb +102 -0
- data/lib/smithy-client/http/error_inspector.rb +87 -0
- data/lib/smithy-client/http/headers.rb +122 -0
- data/lib/smithy-client/http/request.rb +57 -0
- data/lib/smithy-client/http/response.rb +178 -0
- data/lib/smithy-client/http_api_key_provider.rb +18 -0
- data/lib/smithy-client/http_bearer_provider.rb +18 -0
- data/lib/smithy-client/http_login_provider.rb +19 -0
- data/lib/smithy-client/identities/anonymous.rb +10 -0
- data/lib/smithy-client/identities/http_api_key.rb +18 -0
- data/lib/smithy-client/identities/http_bearer.rb +18 -0
- data/lib/smithy-client/identities/http_login.rb +22 -0
- data/lib/smithy-client/identity.rb +15 -0
- data/lib/smithy-client/log_formatter.rb +215 -0
- data/lib/smithy-client/log_param_filter.rb +88 -0
- data/lib/smithy-client/log_param_formatter.rb +65 -0
- data/lib/smithy-client/managed_file.rb +14 -0
- data/lib/smithy-client/net_http/connection_pool.rb +297 -0
- data/lib/smithy-client/net_http/handler.rb +160 -0
- data/lib/smithy-client/net_http/patches.rb +28 -0
- data/lib/smithy-client/networking_error.rb +16 -0
- data/lib/smithy-client/pageable_response.rb +138 -0
- data/lib/smithy-client/param_converter.rb +243 -0
- data/lib/smithy-client/param_validator.rb +213 -0
- data/lib/smithy-client/plugin.rb +144 -0
- data/lib/smithy-client/plugin_list.rb +141 -0
- data/lib/smithy-client/plugins/anonymous_auth.rb +23 -0
- data/lib/smithy-client/plugins/checksum_required.rb +51 -0
- data/lib/smithy-client/plugins/content_length.rb +26 -0
- data/lib/smithy-client/plugins/default_params.rb +22 -0
- data/lib/smithy-client/plugins/host_prefix.rb +69 -0
- data/lib/smithy-client/plugins/http_api_key_auth.rb +37 -0
- data/lib/smithy-client/plugins/http_basic_auth.rb +47 -0
- data/lib/smithy-client/plugins/http_bearer_auth.rb +37 -0
- data/lib/smithy-client/plugins/http_digest_auth.rb +60 -0
- data/lib/smithy-client/plugins/idempotency_token.rb +34 -0
- data/lib/smithy-client/plugins/logging.rb +56 -0
- data/lib/smithy-client/plugins/net_http.rb +163 -0
- data/lib/smithy-client/plugins/pageable_response.rb +37 -0
- data/lib/smithy-client/plugins/param_converter.rb +32 -0
- data/lib/smithy-client/plugins/param_validator.rb +30 -0
- data/lib/smithy-client/plugins/protocol.rb +66 -0
- data/lib/smithy-client/plugins/raise_response_errors.rb +33 -0
- data/lib/smithy-client/plugins/request_compression.rb +200 -0
- data/lib/smithy-client/plugins/response_target.rb +71 -0
- data/lib/smithy-client/plugins/retry_errors.rb +125 -0
- data/lib/smithy-client/plugins/sign_requests.rb +24 -0
- data/lib/smithy-client/plugins/stub_responses.rb +102 -0
- data/lib/smithy-client/protocol_spec_matcher.rb +60 -0
- data/lib/smithy-client/refreshing_identity_provider.rb +65 -0
- data/lib/smithy-client/request.rb +76 -0
- data/lib/smithy-client/response.rb +48 -0
- data/lib/smithy-client/retry/adaptive.rb +66 -0
- data/lib/smithy-client/retry/client_rate_limiter.rb +142 -0
- data/lib/smithy-client/retry/quota.rb +58 -0
- data/lib/smithy-client/retry/standard.rb +52 -0
- data/lib/smithy-client/retry.rb +36 -0
- data/lib/smithy-client/rpc_v2_cbor/protocol.rb +38 -0
- data/lib/smithy-client/rpc_v2_cbor/request_builder.rb +76 -0
- data/lib/smithy-client/rpc_v2_cbor/response_parser.rb +86 -0
- data/lib/smithy-client/rpc_v2_cbor/response_stubber.rb +34 -0
- data/lib/smithy-client/service_error.rb +57 -0
- data/lib/smithy-client/signer.rb +16 -0
- data/lib/smithy-client/signers/anonymous.rb +13 -0
- data/lib/smithy-client/signers/http_api_key.rb +52 -0
- data/lib/smithy-client/signers/http_basic.rb +23 -0
- data/lib/smithy-client/signers/http_bearer.rb +19 -0
- data/lib/smithy-client/signers/http_digest.rb +21 -0
- data/lib/smithy-client/stubbing/data_applicator.rb +61 -0
- data/lib/smithy-client/stubbing/empty_stub.rb +69 -0
- data/lib/smithy-client/stubbing/endpoint_provider.rb +22 -0
- data/lib/smithy-client/stubbing/protocol.rb +29 -0
- data/lib/smithy-client/stubbing/stub_data.rb +25 -0
- data/lib/smithy-client/stubbing.rb +14 -0
- data/lib/smithy-client/stubs.rb +212 -0
- data/lib/smithy-client/util.rb +15 -0
- data/lib/smithy-client/waiters/poller.rb +93 -0
- data/lib/smithy-client/waiters/waiter.rb +113 -0
- data/lib/smithy-client.rb +66 -1
- metadata +163 -9
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'base64'
|
4
|
+
require 'time'
|
5
|
+
|
6
|
+
module Smithy
|
7
|
+
module Client
|
8
|
+
# @api private
|
9
|
+
class DefaultParams
|
10
|
+
include Schema::Shapes
|
11
|
+
|
12
|
+
def initialize(ref)
|
13
|
+
@ref = ref
|
14
|
+
end
|
15
|
+
|
16
|
+
# @param [Hash] params
|
17
|
+
# @return [Hash]
|
18
|
+
def apply(params)
|
19
|
+
structure(@ref, params)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def shape(ref, value)
|
25
|
+
case ref.shape
|
26
|
+
when ListShape then list(ref, value)
|
27
|
+
when MapShape then map(ref, value)
|
28
|
+
when StructureShape then structure(ref, value)
|
29
|
+
else value
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def list(ref, values)
|
34
|
+
return if values.nil?
|
35
|
+
|
36
|
+
shape = ref.shape
|
37
|
+
values.each do |value|
|
38
|
+
shape(shape.member, value)
|
39
|
+
end
|
40
|
+
values
|
41
|
+
end
|
42
|
+
|
43
|
+
def map(ref, values)
|
44
|
+
return if values.nil?
|
45
|
+
|
46
|
+
shape = ref.shape
|
47
|
+
values.each_pair do |_key, value|
|
48
|
+
shape(shape.value, value)
|
49
|
+
end
|
50
|
+
values
|
51
|
+
end
|
52
|
+
|
53
|
+
def structure(ref, values)
|
54
|
+
return if values.nil?
|
55
|
+
|
56
|
+
ref.shape.members.each do |member_name, member_ref|
|
57
|
+
value = values[member_name]
|
58
|
+
value ||= default(member_ref) if default?(ref, member_ref.traits)
|
59
|
+
next if value.nil? && !default?(ref, member_ref.traits) # default can have nil values
|
60
|
+
|
61
|
+
values[member_name] = shape(member_ref, value)
|
62
|
+
end
|
63
|
+
values
|
64
|
+
end
|
65
|
+
|
66
|
+
def default?(ref, traits)
|
67
|
+
# skip defaults for top level members
|
68
|
+
return false if ref == @ref
|
69
|
+
|
70
|
+
traits.include?('smithy.api#default') && !traits.include?('smithy.api#clientOptional')
|
71
|
+
end
|
72
|
+
|
73
|
+
def default(ref)
|
74
|
+
default = ref.traits['smithy.api#default']
|
75
|
+
case ref.shape
|
76
|
+
when BlobShape then Base64.strict_decode64(default)
|
77
|
+
when TimestampShape then timestamp_default(default)
|
78
|
+
else default
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def timestamp_default(default)
|
83
|
+
case default
|
84
|
+
when String then Time.parse(default)
|
85
|
+
when Integer then Time.at(default)
|
86
|
+
else raise ArgumentError, "Invalid default value for Timestamp: #{default.inspect}"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Smithy
|
4
|
+
module Client
|
5
|
+
# This module is mixed into generated Errors modules, providing dynamic
|
6
|
+
# error classes. Error classes all inherit from {ServiceError}.
|
7
|
+
#
|
8
|
+
# # Creates and returns the class
|
9
|
+
# Weather::Errors::MyNewErrorClass
|
10
|
+
#
|
11
|
+
# Since the complete list of possible errors returned by services may
|
12
|
+
# not be known, this allows us to create them as needed. This also
|
13
|
+
# allows users to rescue errors by class without them being concrete
|
14
|
+
# classes beforehand.
|
15
|
+
#
|
16
|
+
# @api private
|
17
|
+
module DynamicErrors
|
18
|
+
def self.extended(submodule)
|
19
|
+
submodule.instance_variable_set('@const_set_mutex', Mutex.new)
|
20
|
+
submodule.const_set(:ServiceError, Class.new(ServiceError))
|
21
|
+
end
|
22
|
+
|
23
|
+
def const_missing(constant)
|
24
|
+
set_error_constant(constant)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Given the name of a service and an error code, this method
|
28
|
+
# returns an error class that extends {ServiceError}.
|
29
|
+
#
|
30
|
+
# Weather::Errors.error_class('NoSuchCity').new
|
31
|
+
# #=> #<Weather::Errors::NoSuchCity>
|
32
|
+
#
|
33
|
+
# @api private
|
34
|
+
def error_class(error_code)
|
35
|
+
constant = error_class_constant(error_code)
|
36
|
+
if error_const_set?(constant)
|
37
|
+
err_class = const_get(constant)
|
38
|
+
err_class.code = constant.to_s
|
39
|
+
err_class
|
40
|
+
else
|
41
|
+
set_error_constant(constant)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
# Convert an error code to an error class name/constant.
|
48
|
+
# This requires filtering non-safe characters from the constant
|
49
|
+
# name and ensuring it begins with an uppercase letter.
|
50
|
+
#
|
51
|
+
# @param [String] error_code
|
52
|
+
# @return [Symbol] Returns a symbolized constant name for the given `error_code`.
|
53
|
+
def error_class_constant(error_code)
|
54
|
+
constant = error_code.to_s
|
55
|
+
constant = constant.gsub(/https?:.*$/, '')
|
56
|
+
constant = constant.gsub(/[^a-zA-Z0-9]/, '')
|
57
|
+
constant = "Error#{constant}" unless constant.match(/^[a-z]/i)
|
58
|
+
constant = constant[0].upcase + constant[1..]
|
59
|
+
constant.to_sym
|
60
|
+
end
|
61
|
+
|
62
|
+
def set_error_constant(constant) # rubocop:disable Naming/AccessorMethodName
|
63
|
+
@const_set_mutex.synchronize do
|
64
|
+
# Ensure the const was not defined while blocked by the mutex
|
65
|
+
if error_const_set?(constant)
|
66
|
+
const_get(constant)
|
67
|
+
else
|
68
|
+
error_class = Class.new(const_get(:ServiceError))
|
69
|
+
error_class.code = constant.to_s
|
70
|
+
const_set(constant, error_class)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def error_const_set?(constant)
|
76
|
+
# Purposefully not using #const_defined? as that method returns true
|
77
|
+
# for constants not defined directly in the current module.
|
78
|
+
constants.include?(constant.to_sym)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,186 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cgi'
|
4
|
+
require 'ipaddr'
|
5
|
+
require 'uri'
|
6
|
+
|
7
|
+
module Smithy
|
8
|
+
module Client
|
9
|
+
# Functions in the Smithy rules engine are named routines that
|
10
|
+
# operate on a finite set of specified inputs, returning an output.
|
11
|
+
# The rules engine has a set of included functions that can be
|
12
|
+
# invoked without additional dependencies, called the standard library.
|
13
|
+
module EndpointRules
|
14
|
+
# Regex that extracts anything in square brackets
|
15
|
+
BRACKET_REGEX = /\[(.*?)\]/
|
16
|
+
|
17
|
+
# An Endpoint resolved by an EndpointProvider
|
18
|
+
class Endpoint
|
19
|
+
# @param [String] uri
|
20
|
+
# @param [Hash] properties ({})
|
21
|
+
# @param [Hash] headers ({})
|
22
|
+
def initialize(uri:, properties: {}, headers: {})
|
23
|
+
@uri = uri
|
24
|
+
@properties = properties
|
25
|
+
@headers = headers
|
26
|
+
end
|
27
|
+
|
28
|
+
# The URI of the endpoint.
|
29
|
+
# @return [String]
|
30
|
+
attr_accessor :uri
|
31
|
+
|
32
|
+
# The authentication schemes supported by the endpoint.
|
33
|
+
# @return [Hash]
|
34
|
+
attr_accessor :properties
|
35
|
+
|
36
|
+
# The headers to include in requests to the endpoint.
|
37
|
+
# @return [Hash]
|
38
|
+
attr_accessor :headers
|
39
|
+
end
|
40
|
+
|
41
|
+
# Evaluates whether the input string is a compliant RFC 1123 host segment.
|
42
|
+
# When allowSubDomains is true, evaluates whether the input string is
|
43
|
+
# composed of values that are each compliant RFC 1123 host segments
|
44
|
+
# joined by dot (.) characters.
|
45
|
+
# @api private
|
46
|
+
def self.valid_host_label?(value, allow_sub_domains)
|
47
|
+
return false if value.empty?
|
48
|
+
|
49
|
+
if allow_sub_domains
|
50
|
+
labels = value.split('.', -1)
|
51
|
+
return labels.all? { |l| valid_host_label?(l, false) }
|
52
|
+
end
|
53
|
+
|
54
|
+
!!(value =~ /\A(?!-)[a-zA-Z0-9-]{1,63}(?<!-)\z/)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Computes a URL structure given an input string.
|
58
|
+
# @api private
|
59
|
+
def self.parse_url(value)
|
60
|
+
URL.new(value).as_json
|
61
|
+
rescue ArgumentError, URI::InvalidURIError
|
62
|
+
nil
|
63
|
+
end
|
64
|
+
|
65
|
+
# Computes a portion of a given string based on
|
66
|
+
# the provided start and end indices.
|
67
|
+
# @api private
|
68
|
+
def self.substring(input, start, stop, reverse)
|
69
|
+
return nil if start >= stop || input.size < stop
|
70
|
+
|
71
|
+
return nil if input.chars.any? { |c| c.ord > 127 }
|
72
|
+
|
73
|
+
return input[start...stop] unless reverse
|
74
|
+
|
75
|
+
r_start = input.size - stop
|
76
|
+
r_stop = input.size - start
|
77
|
+
input[r_start...r_stop]
|
78
|
+
end
|
79
|
+
|
80
|
+
# Performs RFC 3986#section-2.1 defined percent-encoding on the input value.
|
81
|
+
# @api private
|
82
|
+
def self.uri_encode(value)
|
83
|
+
CGI.escape(value.encode('UTF-8')).gsub('+', '%20').gsub('%7E', '~')
|
84
|
+
end
|
85
|
+
|
86
|
+
# isSet(value: Option<T>) bool
|
87
|
+
# @api private
|
88
|
+
def self.set?(value)
|
89
|
+
!value.nil?
|
90
|
+
end
|
91
|
+
|
92
|
+
# not(value: bool) bool
|
93
|
+
# @api private
|
94
|
+
def self.not?(bool)
|
95
|
+
!bool
|
96
|
+
end
|
97
|
+
|
98
|
+
# getAttr(value: Object | Array, path: string) Document
|
99
|
+
# @api private
|
100
|
+
def self.attr(value, path)
|
101
|
+
parts = path.split('.')
|
102
|
+
|
103
|
+
val = attr_brackets(parts, value)
|
104
|
+
|
105
|
+
if parts.size == 1
|
106
|
+
val
|
107
|
+
else
|
108
|
+
attr(val, parts.slice(1..-1).join('.')) # rubocop:disable Style/Attr
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# stringEquals(value1: string, value2: string) bool
|
113
|
+
# @api private
|
114
|
+
def self.string_equals?(value1, value2)
|
115
|
+
value1 == value2
|
116
|
+
end
|
117
|
+
|
118
|
+
# booleanEquals(value1: bool, value2: bool) bool
|
119
|
+
# @api private
|
120
|
+
def self.boolean_equals?(value1, value2)
|
121
|
+
value1 == value2
|
122
|
+
end
|
123
|
+
|
124
|
+
# @api private
|
125
|
+
class URL
|
126
|
+
def initialize(url)
|
127
|
+
uri = URI(url)
|
128
|
+
@scheme = uri.scheme
|
129
|
+
# only support http and https schemes
|
130
|
+
raise ArgumentError unless %w[https http].include?(@scheme)
|
131
|
+
|
132
|
+
# do not support query
|
133
|
+
raise ArgumentError if uri.query
|
134
|
+
|
135
|
+
@authority = extract_authority(url, uri)
|
136
|
+
@path = uri.path
|
137
|
+
@normalized_path = uri.path + (uri.path[-1] == '/' ? '' : '/')
|
138
|
+
@is_ip = ip?(uri.host)
|
139
|
+
end
|
140
|
+
|
141
|
+
attr_reader :scheme, :authority, :path, :normalized_path, :is_ip
|
142
|
+
|
143
|
+
def as_json(*)
|
144
|
+
{
|
145
|
+
'scheme' => scheme,
|
146
|
+
'authority' => authority,
|
147
|
+
'path' => path,
|
148
|
+
'normalizedPath' => normalized_path,
|
149
|
+
'isIp' => is_ip
|
150
|
+
}
|
151
|
+
end
|
152
|
+
|
153
|
+
private
|
154
|
+
|
155
|
+
def extract_authority(url, uri)
|
156
|
+
# don't include port if it's default and not parsed originally
|
157
|
+
if uri.default_port == uri.port && !url.include?(":#{uri.port}")
|
158
|
+
uri.host
|
159
|
+
else
|
160
|
+
"#{uri.host}:#{uri.port}"
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def ip?(authority)
|
165
|
+
IPAddr.new(authority)
|
166
|
+
true
|
167
|
+
rescue IPAddr::InvalidAddressError
|
168
|
+
false
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def self.attr_brackets(parts, value)
|
173
|
+
if (index = parts.first[BRACKET_REGEX, 1])
|
174
|
+
# remove brackets and index from part before indexing
|
175
|
+
if (base = parts.first.gsub(BRACKET_REGEX, '')) && !base.empty?
|
176
|
+
value[base][index.to_i]
|
177
|
+
else
|
178
|
+
value[index.to_i]
|
179
|
+
end
|
180
|
+
else
|
181
|
+
value[parts.first]
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Smithy
|
4
|
+
module Client
|
5
|
+
# Base class for handlers in the client stack.
|
6
|
+
class Handler
|
7
|
+
# @param [Handler] handler (nil) The next handler in the stack that
|
8
|
+
# should be called from within the {#call} method. This value
|
9
|
+
# must only be nil for send handlers.
|
10
|
+
def initialize(handler = nil)
|
11
|
+
@handler = handler
|
12
|
+
end
|
13
|
+
|
14
|
+
# @return [Handler, nil]
|
15
|
+
attr_accessor :handler
|
16
|
+
|
17
|
+
# @param [Smithy::Client::HandlerContext] context
|
18
|
+
# @return [Smithy::Client::Response]
|
19
|
+
def call(context)
|
20
|
+
@handler.call(context)
|
21
|
+
end
|
22
|
+
|
23
|
+
# @api private
|
24
|
+
def inspect
|
25
|
+
"#<#{self.class.name || 'UnknownHandler'} @handler=#{@handler.inspect}>"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Smithy
|
4
|
+
module Client
|
5
|
+
# This module provides the ability to add handlers to a class or
|
6
|
+
# module. The including class or extending module must respond to
|
7
|
+
# `#handlers`, returning a {HandlerList}.
|
8
|
+
module HandlerBuilder
|
9
|
+
def handle(*args, &block)
|
10
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
11
|
+
handler_class = block ? handler_for(*args, &block) : args.first
|
12
|
+
handlers.add(handler_class, options)
|
13
|
+
end
|
14
|
+
alias handler handle
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def handler_for(name = nil, &block)
|
19
|
+
if name
|
20
|
+
const_set(name, new_handler(block))
|
21
|
+
else
|
22
|
+
new_handler(block)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def new_handler(block)
|
27
|
+
Class.new(Handler) do
|
28
|
+
define_method(:call, &block)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Smithy
|
4
|
+
module Client
|
5
|
+
# Context that is passed to handlers during execution.
|
6
|
+
class HandlerContext
|
7
|
+
# @option options [Symbol] :operation_name (nil)
|
8
|
+
# @option options [OperationShape] :operation (nil)
|
9
|
+
# @option options [Base] :client (nil)
|
10
|
+
# @option options [Hash] :params ({})
|
11
|
+
# @option options [Configuration] :config (nil)
|
12
|
+
# @option options [HTTP::Request] :http_request (HTTP::Request.new)
|
13
|
+
# @option options [HTTP::Response] :http_response (HTTP::Response.new)
|
14
|
+
# @option options [Hash] :metadata ({})
|
15
|
+
def initialize(options = {})
|
16
|
+
@operation_name = options[:operation_name]
|
17
|
+
@operation = options[:operation]
|
18
|
+
@client = options[:client]
|
19
|
+
@params = options[:params] || {}
|
20
|
+
@config = options[:config]
|
21
|
+
@http_request = options[:http_request] || HTTP::Request.new
|
22
|
+
@http_response = options[:http_response] || HTTP::Response.new
|
23
|
+
@retries = 0
|
24
|
+
@metadata = {}
|
25
|
+
end
|
26
|
+
|
27
|
+
# @return [Symbol] Name of the API operation called.
|
28
|
+
attr_accessor :operation_name
|
29
|
+
|
30
|
+
# @return [OperationShape] Shape of the Operation called.
|
31
|
+
attr_accessor :operation
|
32
|
+
|
33
|
+
# @return [Base]
|
34
|
+
attr_accessor :client
|
35
|
+
|
36
|
+
# @return [Hash] The request parameters as a Hash.
|
37
|
+
attr_accessor :params
|
38
|
+
|
39
|
+
# @return [Struct] The client configuration.
|
40
|
+
attr_accessor :config
|
41
|
+
|
42
|
+
# @return [HTTP::Request]
|
43
|
+
attr_accessor :http_request
|
44
|
+
|
45
|
+
# @return [HTTP::Response]
|
46
|
+
attr_accessor :http_response
|
47
|
+
|
48
|
+
# @return [Integer]
|
49
|
+
attr_accessor :retries
|
50
|
+
|
51
|
+
# @return [Hash]
|
52
|
+
attr_reader :metadata
|
53
|
+
|
54
|
+
# @param [Symbol] key
|
55
|
+
# @return [Object]
|
56
|
+
def [](key)
|
57
|
+
@metadata[key]
|
58
|
+
end
|
59
|
+
|
60
|
+
# @param [Symbol] key
|
61
|
+
# @param [Object] value
|
62
|
+
def []=(key, value)
|
63
|
+
@metadata[key] = value
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,197 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Smithy
|
4
|
+
module Client
|
5
|
+
# A list of handlers that are used to build a handler stack.
|
6
|
+
class HandlerList
|
7
|
+
include Enumerable
|
8
|
+
|
9
|
+
# @api private
|
10
|
+
def initialize(options = {})
|
11
|
+
@index = options[:index] || 0
|
12
|
+
@entries = {}
|
13
|
+
@mutex = Mutex.new
|
14
|
+
entries = options[:entries] || []
|
15
|
+
add_entries(entries) unless entries.empty?
|
16
|
+
end
|
17
|
+
|
18
|
+
# Yields the handlers in stack order, which is reverse priority.
|
19
|
+
# @yieldparam [Class<Handler>] handler_class
|
20
|
+
def each
|
21
|
+
entries.sort.each do |entry|
|
22
|
+
yield(entry.handler_class) if entry.operations.nil?
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# @return [Array<HandlerListEntry>]
|
27
|
+
def entries
|
28
|
+
@mutex.synchronize do
|
29
|
+
@entries.values
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Registers a handler. Handlers are used to build a handler stack.
|
34
|
+
# Handlers default to the `:build` step with default priority of 50.
|
35
|
+
# The step and priority determine where in the stack a handler
|
36
|
+
# will be.
|
37
|
+
#
|
38
|
+
# ## Handler Stack Ordering
|
39
|
+
#
|
40
|
+
# A handler stack is built from the inside-out. The stack is
|
41
|
+
# seeded with the send handler. Handlers are constructed recursively
|
42
|
+
# in reverse step and priority order so that the highest priority
|
43
|
+
# handler is on the outside.
|
44
|
+
#
|
45
|
+
# By constructing the stack from the inside-out, this ensures
|
46
|
+
# that the validate handlers will be called first and the sign handlers
|
47
|
+
# will be called just before the final and only send handler is called.
|
48
|
+
#
|
49
|
+
# ## Steps
|
50
|
+
#
|
51
|
+
# Handlers are ordered first by step. These steps represent the
|
52
|
+
# life-cycle of a request. Valid steps are:
|
53
|
+
#
|
54
|
+
# * `:initialize`
|
55
|
+
# * `:validate`
|
56
|
+
# * `:build`
|
57
|
+
# * `:retry`
|
58
|
+
# * `:parse`
|
59
|
+
# * `:sign`
|
60
|
+
# * `:send`
|
61
|
+
#
|
62
|
+
# Many handlers can be added to the same step, except for `:send`.
|
63
|
+
# There can be only one `:send` handler. Adding an additional
|
64
|
+
# `:send` handler replaces the previous one.
|
65
|
+
#
|
66
|
+
# ## Priorities
|
67
|
+
#
|
68
|
+
# Handlers within a single step are executed in priority order. The
|
69
|
+
# higher the priority, the earlier in the stack the handler will
|
70
|
+
# be called.
|
71
|
+
#
|
72
|
+
# * Handler priority is an integer between 0 and 99, inclusively.
|
73
|
+
# * Handler priority defaults to 50.
|
74
|
+
# * When multiple handlers are added to the same step with the same
|
75
|
+
# priority, the last one added will have the highest priority and
|
76
|
+
# the first one added will have the lowest priority.
|
77
|
+
#
|
78
|
+
# @param [Class<Handler>] handler_class This should be a subclass
|
79
|
+
# of {Handler}.
|
80
|
+
#
|
81
|
+
# @option options [Symbol] :step (:build) The request life-cycle
|
82
|
+
# step the handler should run in. Defaults to `:build`. The
|
83
|
+
# list of possible steps, in high-to-low priority order are:
|
84
|
+
#
|
85
|
+
# * `:initialize`
|
86
|
+
# * `:validate`
|
87
|
+
# * `:build`
|
88
|
+
# * `:retry`
|
89
|
+
# * `:parse`
|
90
|
+
# * `:sign`
|
91
|
+
# * `:send`
|
92
|
+
#
|
93
|
+
# @option options [Integer] :priority (50) The priority of this
|
94
|
+
# handler within a step. The priority must be between 0 and 99
|
95
|
+
# inclusively. It defaults to 50. When two handlers have the
|
96
|
+
# same `:step` and `:priority`, the handler registered last has
|
97
|
+
# the highest priority.
|
98
|
+
#
|
99
|
+
# @option options [Array<Symbol>] :operations A list of
|
100
|
+
# operations names the handler should be applied to. When
|
101
|
+
# `:operations` is omitted, the handler is applied to all
|
102
|
+
# operations for the client.
|
103
|
+
#
|
104
|
+
# @raise ArgumentError if the priority is not between 0 and 99 or
|
105
|
+
# if the step is not a valid step.
|
106
|
+
# @return [Class<Handler>] Returns the handler class that was added.
|
107
|
+
#
|
108
|
+
def add(handler_class, options = {})
|
109
|
+
@mutex.synchronize do
|
110
|
+
add_entry(
|
111
|
+
HandlerListEntry.new(
|
112
|
+
options.merge(
|
113
|
+
handler_class: handler_class,
|
114
|
+
inserted: next_index
|
115
|
+
)
|
116
|
+
)
|
117
|
+
)
|
118
|
+
end
|
119
|
+
handler_class
|
120
|
+
end
|
121
|
+
|
122
|
+
# @param [Class<Handler>] handler_class
|
123
|
+
def remove(handler_class)
|
124
|
+
@mutex.synchronize do
|
125
|
+
@entries.each do |key, entry|
|
126
|
+
remove_entry(key, entry, handler_class)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# Copies handlers from the `source_list` onto the current handler list.
|
132
|
+
# If a block is given, only the entries that return a `true` value
|
133
|
+
# from the block will be copied.
|
134
|
+
# @param [HandlerList] source_list
|
135
|
+
# @return [void]
|
136
|
+
def copy_from(source_list)
|
137
|
+
entries = []
|
138
|
+
source_list.entries.each do |entry|
|
139
|
+
if block_given?
|
140
|
+
entries << entry.copy(inserted: next_index) if yield(entry)
|
141
|
+
else
|
142
|
+
entries << entry.copy(inserted: next_index)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
add_entries(entries)
|
146
|
+
end
|
147
|
+
|
148
|
+
# Returns a handler list for the given operation. The returned
|
149
|
+
# will have the operation specific handlers merged with the common
|
150
|
+
# handlers.
|
151
|
+
# @param [Symbol] operation The name of an operation.
|
152
|
+
# @return [HandlerList]
|
153
|
+
def for(operation)
|
154
|
+
HandlerList.new(index: @index, entries: filter(operation))
|
155
|
+
end
|
156
|
+
|
157
|
+
# Constructs the handlers recursively, building a handler stack.
|
158
|
+
# The `:send` handler will be at the top of the stack and the
|
159
|
+
# `:validate` handlers will be at the bottom.
|
160
|
+
# @return [Handler, nil]
|
161
|
+
def to_stack
|
162
|
+
inject(nil) { |stack, handler| handler.new(stack) }
|
163
|
+
end
|
164
|
+
|
165
|
+
private
|
166
|
+
|
167
|
+
def add_entries(entries)
|
168
|
+
@mutex.synchronize do
|
169
|
+
entries.each { |entry| add_entry(entry) }
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def add_entry(entry)
|
174
|
+
key = entry.step == :send ? :send : entry.object_id
|
175
|
+
@entries[key] = entry
|
176
|
+
end
|
177
|
+
|
178
|
+
def remove_entry(key, entry, handler_class)
|
179
|
+
@entries.delete(key) if entry.handler_class == handler_class
|
180
|
+
end
|
181
|
+
|
182
|
+
def filter(operation)
|
183
|
+
entries.each_with_object([]) do |entry, filtered|
|
184
|
+
if entry.operations.nil?
|
185
|
+
filtered << entry.copy
|
186
|
+
elsif entry.operations.include?(operation)
|
187
|
+
filtered << entry.copy(operations: nil)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def next_index
|
193
|
+
@index += 1
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|