aws-sdk-core 3.114.1 → 3.130.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +216 -0
  3. data/VERSION +1 -1
  4. data/lib/aws-defaults/default_configuration.rb +153 -0
  5. data/lib/aws-defaults/defaults_mode_config_resolver.rb +107 -0
  6. data/lib/aws-defaults.rb +3 -0
  7. data/lib/aws-sdk-core/assume_role_credentials.rb +19 -0
  8. data/lib/aws-sdk-core/assume_role_web_identity_credentials.rb +7 -1
  9. data/lib/aws-sdk-core/client_stubs.rb +5 -1
  10. data/lib/aws-sdk-core/credential_provider_chain.rb +2 -1
  11. data/lib/aws-sdk-core/ec2_metadata.rb +27 -7
  12. data/lib/aws-sdk-core/ecs_credentials.rb +5 -0
  13. data/lib/aws-sdk-core/errors.rb +5 -1
  14. data/lib/aws-sdk-core/instance_profile_credentials.rb +119 -18
  15. data/lib/aws-sdk-core/json/json_engine.rb +10 -8
  16. data/lib/aws-sdk-core/json/oj_engine.rb +33 -6
  17. data/lib/aws-sdk-core/json/parser.rb +8 -0
  18. data/lib/aws-sdk-core/json.rb +8 -26
  19. data/lib/aws-sdk-core/log/param_filter.rb +9 -1
  20. data/lib/aws-sdk-core/pageable_response.rb +72 -26
  21. data/lib/aws-sdk-core/pager.rb +3 -0
  22. data/lib/aws-sdk-core/param_validator.rb +29 -0
  23. data/lib/aws-sdk-core/plugins/checksum_algorithm.rb +340 -0
  24. data/lib/aws-sdk-core/plugins/credentials_configuration.rb +3 -1
  25. data/lib/aws-sdk-core/plugins/defaults_mode.rb +40 -0
  26. data/lib/aws-sdk-core/plugins/http_checksum.rb +8 -1
  27. data/lib/aws-sdk-core/plugins/protocols/api_gateway.rb +17 -0
  28. data/lib/aws-sdk-core/plugins/protocols/rest_json.rb +16 -1
  29. data/lib/aws-sdk-core/plugins/recursion_detection.rb +27 -0
  30. data/lib/aws-sdk-core/plugins/regional_endpoint.rb +47 -1
  31. data/lib/aws-sdk-core/plugins/response_paging.rb +1 -1
  32. data/lib/aws-sdk-core/plugins/retries/error_inspector.rb +5 -3
  33. data/lib/aws-sdk-core/plugins/retry_errors.rb +21 -5
  34. data/lib/aws-sdk-core/plugins/signature_v4.rb +15 -24
  35. data/lib/aws-sdk-core/plugins/stub_responses.rb +5 -1
  36. data/lib/aws-sdk-core/process_credentials.rb +3 -2
  37. data/lib/aws-sdk-core/refreshing_credentials.rb +40 -11
  38. data/lib/aws-sdk-core/rest/request/body.rb +19 -1
  39. data/lib/aws-sdk-core/rest/request/headers.rb +18 -6
  40. data/lib/aws-sdk-core/rest/response/headers.rb +3 -1
  41. data/lib/aws-sdk-core/shared_config.rb +27 -8
  42. data/lib/aws-sdk-core/shared_credentials.rb +7 -1
  43. data/lib/aws-sdk-core/sso_credentials.rb +8 -3
  44. data/lib/aws-sdk-core/structure.rb +10 -1
  45. data/lib/aws-sdk-core/xml/parser/engines/ox.rb +1 -1
  46. data/lib/aws-sdk-core/xml/parser/engines/rexml.rb +0 -8
  47. data/lib/aws-sdk-core/xml/parser/frame.rb +23 -0
  48. data/lib/aws-sdk-core.rb +6 -0
  49. data/lib/aws-sdk-sso/client.rb +27 -5
  50. data/lib/aws-sdk-sso.rb +1 -1
  51. data/lib/aws-sdk-sts/client.rb +424 -415
  52. data/lib/aws-sdk-sts/plugins/sts_regional_endpoints.rb +5 -1
  53. data/lib/aws-sdk-sts/presigner.rb +7 -1
  54. data/lib/aws-sdk-sts/types.rb +199 -181
  55. data/lib/aws-sdk-sts.rb +1 -1
  56. data/lib/seahorse/client/configuration.rb +4 -0
  57. data/lib/seahorse/client/h2/connection.rb +14 -11
  58. data/lib/seahorse/client/h2/handler.rb +4 -5
  59. data/lib/seahorse/client/net_http/connection_pool.rb +7 -0
  60. data/lib/seahorse/client/net_http/handler.rb +15 -7
  61. data/lib/seahorse/client/net_http/patches.rb +13 -84
  62. data/lib/seahorse/client/plugins/content_length.rb +11 -5
  63. data/lib/seahorse/client/plugins/net_http.rb +33 -2
  64. data/lib/seahorse/model/operation.rb +3 -0
  65. data/lib/seahorse/model/shapes.rb +25 -0
  66. metadata +11 -6
  67. data/lib/aws-sdk-sso/plugins/content_type.rb +0 -25
@@ -43,6 +43,10 @@ module Aws
43
43
  # @option options [IO] :http_debug_output (nil) HTTP wire
44
44
  # traces are sent to this object. You can specify something
45
45
  # like $stdout.
46
+ # @option options [Callable] before_refresh Proc called before
47
+ # credentials are refreshed. `before_refresh` is called
48
+ # with an instance of this object when
49
+ # AWS credentials are required and need to be refreshed.
46
50
  def initialize options = {}
47
51
  @retries = options[:retries] || 5
48
52
  @ip_address = options[:ip_address] || '169.254.170.2'
@@ -58,6 +62,7 @@ module Aws
58
62
  @http_read_timeout = options[:http_read_timeout] || 5
59
63
  @http_debug_output = options[:http_debug_output]
60
64
  @backoff = backoff(options[:backoff])
65
+ @async_refresh = false
61
66
  super
62
67
  end
63
68
 
@@ -18,7 +18,7 @@ module Aws
18
18
  @code = self.class.code
19
19
  @context = context
20
20
  @data = data
21
- @message = message && !message.empty? ? message : self.class
21
+ @message = message && !message.empty? ? message : self.class.to_s
22
22
  super(@message)
23
23
  end
24
24
 
@@ -210,6 +210,10 @@ module Aws
210
210
  # Raised when SSO Credentials are invalid
211
211
  class InvalidSSOCredentials < RuntimeError; end
212
212
 
213
+ # Raised when there is a circular reference in chained
214
+ # source_profiles
215
+ class SourceProfileCircularReferenceError < RuntimeError; end
216
+
213
217
  # Raised when a client is constructed and region is not specified.
214
218
  class MissingRegionError < ArgumentError
215
219
  def initialize(*args)
@@ -5,7 +5,6 @@ require 'net/http'
5
5
 
6
6
  module Aws
7
7
  class InstanceProfileCredentials
8
-
9
8
  include CredentialProvider
10
9
  include RefreshingCredentials
11
10
 
@@ -44,7 +43,13 @@ module Aws
44
43
  # @param [Hash] options
45
44
  # @option options [Integer] :retries (1) Number of times to retry
46
45
  # when retrieving credentials.
47
- # @option options [String] :ip_address ('169.254.169.254')
46
+ # @option options [String] :endpoint ('http://169.254.169.254') The IMDS
47
+ # endpoint. This option has precedence over the :endpoint_mode.
48
+ # @option options [String] :endpoint_mode ('IPv4') The endpoint mode for
49
+ # the instance metadata service. This is either 'IPv4' ('169.254.169.254')
50
+ # or 'IPv6' ('[fd00:ec2::254]').
51
+ # @option options [String] :ip_address ('169.254.169.254') Deprecated. Use
52
+ # :endpoint instead. The IP address for the endpoint.
48
53
  # @option options [Integer] :port (80)
49
54
  # @option options [Float] :http_open_timeout (1)
50
55
  # @option options [Float] :http_read_timeout (1)
@@ -58,9 +63,14 @@ module Aws
58
63
  # @option options [Integer] :token_ttl Time-to-Live in seconds for EC2
59
64
  # Metadata Token used for fetching Metadata Profile Credentials, defaults
60
65
  # to 21600 seconds
66
+ # @option options [Callable] before_refresh Proc called before
67
+ # credentials are refreshed. `before_refresh` is called
68
+ # with an instance of this object when
69
+ # AWS credentials are required and need to be refreshed.
61
70
  def initialize(options = {})
62
71
  @retries = options[:retries] || 1
63
- @ip_address = options[:ip_address] || '169.254.169.254'
72
+ endpoint_mode = resolve_endpoint_mode(options)
73
+ @endpoint = resolve_endpoint(options, endpoint_mode)
64
74
  @port = options[:port] || 80
65
75
  @http_open_timeout = options[:http_open_timeout] || 1
66
76
  @http_read_timeout = options[:http_read_timeout] || 1
@@ -68,6 +78,8 @@ module Aws
68
78
  @backoff = backoff(options[:backoff])
69
79
  @token_ttl = options[:token_ttl] || 21_600
70
80
  @token = nil
81
+ @no_refresh_until = nil
82
+ @async_refresh = false
71
83
  super
72
84
  end
73
85
 
@@ -78,6 +90,34 @@ module Aws
78
90
 
79
91
  private
80
92
 
93
+ def resolve_endpoint_mode(options)
94
+ value = options[:endpoint_mode]
95
+ value ||= ENV['AWS_EC2_METADATA_SERVICE_ENDPOINT_MODE']
96
+ value ||= Aws.shared_config.ec2_metadata_service_endpoint_mode(
97
+ profile: options[:profile]
98
+ )
99
+ value || 'IPv4'
100
+ end
101
+
102
+ def resolve_endpoint(options, endpoint_mode)
103
+ value = options[:endpoint] || options[:ip_address]
104
+ value ||= ENV['AWS_EC2_METADATA_SERVICE_ENDPOINT']
105
+ value ||= Aws.shared_config.ec2_metadata_service_endpoint(
106
+ profile: options[:profile]
107
+ )
108
+
109
+ return value if value
110
+
111
+ case endpoint_mode.downcase
112
+ when 'ipv4' then 'http://169.254.169.254'
113
+ when 'ipv6' then 'http://[fd00:ec2::254]'
114
+ else
115
+ raise ArgumentError,
116
+ ':endpoint_mode is not valid, expected IPv4 or IPv6, '\
117
+ "got: #{endpoint_mode}"
118
+ end
119
+ end
120
+
81
121
  def backoff(backoff)
82
122
  case backoff
83
123
  when Proc then backoff
@@ -87,18 +127,48 @@ module Aws
87
127
  end
88
128
 
89
129
  def refresh
130
+ if @no_refresh_until && @no_refresh_until > Time.now
131
+ warn_expired_credentials
132
+ return
133
+ end
134
+
90
135
  # Retry loading credentials up to 3 times is the instance metadata
91
136
  # service is responding but is returning invalid JSON documents
92
137
  # in response to the GET profile credentials call.
93
138
  begin
94
139
  retry_errors([Aws::Json::ParseError, StandardError], max_retries: 3) do
95
140
  c = Aws::Json.load(get_credentials.to_s)
96
- @credentials = Credentials.new(
97
- c['AccessKeyId'],
98
- c['SecretAccessKey'],
99
- c['Token']
100
- )
101
- @expiration = c['Expiration'] ? Time.iso8601(c['Expiration']) : nil
141
+ if empty_credentials?(@credentials)
142
+ @credentials = Credentials.new(
143
+ c['AccessKeyId'],
144
+ c['SecretAccessKey'],
145
+ c['Token']
146
+ )
147
+ @expiration = c['Expiration'] ? Time.iso8601(c['Expiration']) : nil
148
+ if @expiration && @expiration < Time.now
149
+ @no_refresh_until = Time.now + refresh_offset
150
+ warn_expired_credentials
151
+ end
152
+ else
153
+ # credentials are already set, update them only if the new ones are not empty
154
+ if !c['AccessKeyId'] || c['AccessKeyId'].empty?
155
+ # error getting new credentials
156
+ @no_refresh_until = Time.now + refresh_offset
157
+ warn_expired_credentials
158
+ else
159
+ @credentials = Credentials.new(
160
+ c['AccessKeyId'],
161
+ c['SecretAccessKey'],
162
+ c['Token']
163
+ )
164
+ @expiration = c['Expiration'] ? Time.iso8601(c['Expiration']) : nil
165
+ if @expiration && @expiration < Time.now
166
+ @no_refresh_until = Time.now + refresh_offset
167
+ warn_expired_credentials
168
+ end
169
+ end
170
+ end
171
+
102
172
  end
103
173
  rescue Aws::Json::ParseError
104
174
  raise Aws::Errors::MetadataParserError
@@ -119,10 +189,11 @@ module Aws
119
189
  begin
120
190
  retry_errors(NETWORK_ERRORS, max_retries: @retries) do
121
191
  unless token_set?
192
+ created_time = Time.now
122
193
  token_value, ttl = http_put(
123
194
  conn, METADATA_TOKEN_PATH, @token_ttl
124
195
  )
125
- @token = Token.new(token_value, ttl) if token_value && ttl
196
+ @token = Token.new(token_value, ttl, created_time) if token_value && ttl
126
197
  end
127
198
  end
128
199
  rescue *NETWORK_ERRORS
@@ -132,9 +203,17 @@ module Aws
132
203
  end
133
204
 
134
205
  token = @token.value if token_set?
135
- metadata = http_get(conn, METADATA_PATH_BASE, token)
136
- profile_name = metadata.lines.first.strip
137
- http_get(conn, METADATA_PATH_BASE + profile_name, token)
206
+
207
+ begin
208
+ metadata = http_get(conn, METADATA_PATH_BASE, token)
209
+ profile_name = metadata.lines.first.strip
210
+ http_get(conn, METADATA_PATH_BASE + profile_name, token)
211
+ rescue TokenExpiredError
212
+ # Token has expired, reset it
213
+ # The next retry should fetch it
214
+ @token = nil
215
+ raise Non200Response
216
+ end
138
217
  end
139
218
  end
140
219
  rescue
@@ -152,7 +231,8 @@ module Aws
152
231
  end
153
232
 
154
233
  def open_connection
155
- http = Net::HTTP.new(@ip_address, @port, nil)
234
+ uri = URI.parse(@endpoint)
235
+ http = Net::HTTP.new(uri.hostname || @endpoint, @port || uri.port)
156
236
  http.open_timeout = @http_open_timeout
157
237
  http.read_timeout = @http_read_timeout
158
238
  http.set_debug_output(@http_debug_output) if @http_debug_output
@@ -165,9 +245,15 @@ module Aws
165
245
  headers = { 'User-Agent' => "aws-sdk-ruby3/#{CORE_GEM_VERSION}" }
166
246
  headers['x-aws-ec2-metadata-token'] = token if token
167
247
  response = connection.request(Net::HTTP::Get.new(path, headers))
168
- raise Non200Response unless response.code.to_i == 200
169
248
 
170
- response.body
249
+ case response.code.to_i
250
+ when 200
251
+ response.body
252
+ when 401
253
+ raise TokenExpiredError
254
+ else
255
+ raise Non200Response
256
+ end
171
257
  end
172
258
 
173
259
  # PUT request fetch token with ttl
@@ -206,13 +292,28 @@ module Aws
206
292
  end
207
293
  end
208
294
 
295
+ def warn_expired_credentials
296
+ warn("Attempting credential expiration extension due to a credential "\
297
+ "service availability issue. A refresh of these credentials "\
298
+ "will be attempted again in 5 minutes.")
299
+ end
300
+
301
+ def empty_credentials?(creds)
302
+ !creds || !creds.access_key_id || creds.access_key_id.empty?
303
+ end
304
+
305
+ # Compute an offset for refresh with jitter
306
+ def refresh_offset
307
+ 300 + rand(0..60)
308
+ end
309
+
209
310
  # @api private
210
311
  # Token used to fetch IMDS profile and credentials
211
312
  class Token
212
- def initialize(value, ttl)
313
+ def initialize(value, ttl, created_time = Time.now)
213
314
  @ttl = ttl
214
315
  @value = value
215
- @created_time = Time.now
316
+ @created_time = created_time
216
317
  end
217
318
 
218
319
  # [String] token value
@@ -2,16 +2,18 @@
2
2
 
3
3
  module Aws
4
4
  module Json
5
- class JSONEngine
5
+ module JSONEngine
6
+ class << self
7
+ def load(json)
8
+ JSON.parse(json)
9
+ rescue JSON::ParserError => e
10
+ raise ParseError.new(e)
11
+ end
6
12
 
7
- def self.load(json)
8
- JSON.load(json)
13
+ def dump(value)
14
+ JSON.dump(value)
15
+ end
9
16
  end
10
-
11
- def self.dump(value)
12
- JSON.dump(value)
13
- end
14
-
15
17
  end
16
18
  end
17
19
  end
@@ -2,16 +2,43 @@
2
2
 
3
3
  module Aws
4
4
  module Json
5
- class OjEngine
5
+ module OjEngine
6
+ # @api private
7
+ LOAD_OPTIONS = { mode: :compat, symbol_keys: false, empty_string: false }.freeze
6
8
 
7
- def self.load(json)
8
- Oj.load(json)
9
- end
9
+ # @api private
10
+ DUMP_OPTIONS = { mode: :compat }.freeze
11
+
12
+ class << self
13
+ def load(json)
14
+ Oj.load(json, LOAD_OPTIONS)
15
+ rescue *PARSE_ERRORS => e
16
+ raise ParseError.new(e)
17
+ end
18
+
19
+ def dump(value)
20
+ Oj.dump(value, DUMP_OPTIONS)
21
+ end
22
+
23
+ private
24
+
25
+ # Oj before 1.4.0 does not define Oj::ParseError and instead raises
26
+ # SyntaxError on failure
27
+ def detect_oj_parse_errors
28
+ require 'oj'
10
29
 
11
- def self.dump(value)
12
- Oj.dump(value)
30
+ if Oj.const_defined?(:ParseError)
31
+ [Oj::ParseError, EncodingError, JSON::ParserError]
32
+ else
33
+ [SyntaxError]
34
+ end
35
+ rescue LoadError
36
+ nil
37
+ end
13
38
  end
14
39
 
40
+ # @api private
41
+ PARSE_ERRORS = detect_oj_parse_errors
15
42
  end
16
43
  end
17
44
  end
@@ -28,8 +28,16 @@ module Aws
28
28
  member_name, member_ref = shape.member_by_location_name(key)
29
29
  if member_ref
30
30
  target[member_name] = parse_ref(member_ref, value)
31
+ elsif shape.union
32
+ target[:unknown] = { 'name' => key, 'value' => value }
31
33
  end
32
34
  end
35
+ if shape.union
36
+ # convert to subclass
37
+ member_subclass = shape.member_subclass(target.member).new
38
+ member_subclass[target.member] = target.value
39
+ target = member_subclass
40
+ end
33
41
  target
34
42
  end
35
43
 
@@ -5,6 +5,8 @@ require_relative 'json/builder'
5
5
  require_relative 'json/error_handler'
6
6
  require_relative 'json/handler'
7
7
  require_relative 'json/parser'
8
+ require_relative 'json/json_engine'
9
+ require_relative 'json/oj_engine'
8
10
 
9
11
  module Aws
10
12
  # @api private
@@ -20,9 +22,7 @@ module Aws
20
22
 
21
23
  class << self
22
24
  def load(json)
23
- ENGINE.load(json, *ENGINE_LOAD_OPTIONS)
24
- rescue *ENGINE_ERRORS => e
25
- raise ParseError, e
25
+ ENGINE.load(json)
26
26
  end
27
27
 
28
28
  def load_file(path)
@@ -30,38 +30,20 @@ module Aws
30
30
  end
31
31
 
32
32
  def dump(value)
33
- ENGINE.dump(value, *ENGINE_DUMP_OPTIONS)
33
+ ENGINE.dump(value)
34
34
  end
35
35
 
36
36
  private
37
37
 
38
- def oj_engine
38
+ def select_engine
39
39
  require 'oj'
40
- [
41
- Oj,
42
- [{ mode: :compat, symbol_keys: false, empty_string: false }],
43
- [{ mode: :compat }],
44
- oj_parse_error
45
- ]
40
+ OjEngine
46
41
  rescue LoadError
47
- false
48
- end
49
-
50
- def json_engine
51
- [JSON, [], [], [JSON::ParserError]]
52
- end
53
-
54
- def oj_parse_error
55
- if Oj.const_defined?('ParseError')
56
- [Oj::ParseError, EncodingError, JSON::ParserError]
57
- else
58
- [SyntaxError]
59
- end
42
+ JSONEngine
60
43
  end
61
44
  end
62
45
 
63
46
  # @api private
64
- ENGINE, ENGINE_LOAD_OPTIONS, ENGINE_DUMP_OPTIONS, ENGINE_ERRORS =
65
- oj_engine || json_engine
47
+ ENGINE = select_engine
66
48
  end
67
49
  end
@@ -26,7 +26,8 @@ module Aws
26
26
 
27
27
  def filter(values, type)
28
28
  case values
29
- when Struct, Hash then filter_hash(values, type)
29
+ when Struct then filter_struct(values, type)
30
+ when Hash then filter_hash(values, type)
30
31
  when Array then filter_array(values, type)
31
32
  else values
32
33
  end
@@ -34,6 +35,13 @@ module Aws
34
35
 
35
36
  private
36
37
 
38
+ def filter_struct(values, type)
39
+ if values.class.include? Aws::Structure::Union
40
+ values = { values.member => values.value }
41
+ end
42
+ filter_hash(values, type)
43
+ end
44
+
37
45
  def filter_hash(values, type)
38
46
  if type.const_defined?('SENSITIVE')
39
47
  filters = type::SENSITIVE + @additional_filters
@@ -48,11 +48,11 @@ module Aws
48
48
  #
49
49
  module PageableResponse
50
50
 
51
- def self.extended(base)
52
- base.extend Enumerable
53
- base.extend UnsafeEnumerableMethods
54
- base.instance_variable_set("@last_page", nil)
55
- base.instance_variable_set("@more_results", nil)
51
+ def self.apply(base)
52
+ base.extend Extension
53
+ base.instance_variable_set(:@last_page, nil)
54
+ base.instance_variable_set(:@more_results, nil)
55
+ base
56
56
  end
57
57
 
58
58
  # @return [Paging::Pager]
@@ -62,39 +62,26 @@ module Aws
62
62
  # when this method returns `false` will raise an error.
63
63
  # @return [Boolean]
64
64
  def last_page?
65
- if @last_page.nil?
66
- @last_page = !@pager.truncated?(self)
67
- end
68
- @last_page
65
+ # Actual implementation is in PageableResponse::Extension
69
66
  end
70
67
 
71
68
  # Returns `true` if there are more results. Calling {#next_page} will
72
69
  # return the next response.
73
70
  # @return [Boolean]
74
71
  def next_page?
75
- !last_page?
72
+ # Actual implementation is in PageableResponse::Extension
76
73
  end
77
74
 
78
75
  # @return [Seahorse::Client::Response]
79
76
  def next_page(params = {})
80
- if last_page?
81
- raise LastPageError.new(self)
82
- else
83
- next_response(params)
84
- end
77
+ # Actual implementation is in PageableResponse::Extension
85
78
  end
86
79
 
87
80
  # Yields the current and each following response to the given block.
88
81
  # @yieldparam [Response] response
89
82
  # @return [Enumerable,nil] Returns a new Enumerable if no block is given.
90
83
  def each(&block)
91
- return enum_for(:each_page) unless block_given?
92
- response = self
93
- yield(response)
94
- until response.last_page?
95
- response = response.next_page
96
- yield(response)
97
- end
84
+ # Actual implementation is in PageableResponse::Extension
98
85
  end
99
86
  alias each_page each
100
87
 
@@ -105,9 +92,7 @@ module Aws
105
92
  # @return [Seahorse::Client::Response] Returns the next page of
106
93
  # results.
107
94
  def next_response(params)
108
- params = next_page_params(params)
109
- request = context.client.build_request(context.operation_name, params)
110
- request.send_request
95
+ # Actual implementation is in PageableResponse::Extension
111
96
  end
112
97
 
113
98
  # @param [Hash] params A hash of additional request params to
@@ -115,7 +100,7 @@ module Aws
115
100
  # @return [Hash] Returns the hash of request parameters for the
116
101
  # next page, merging any given params.
117
102
  def next_page_params(params)
118
- context[:original_params].merge(@pager.next_tokens(self).merge(params))
103
+ # Actual implementation is in PageableResponse::Extension
119
104
  end
120
105
 
121
106
  # Raised when calling {PageableResponse#next_page} on a pager that
@@ -162,5 +147,66 @@ module Aws
162
147
  end
163
148
 
164
149
  end
150
+
151
+ # The actual decorator module implementation. It is in a distinct module
152
+ # so that it can be used to extend objects without busting Ruby's constant cache.
153
+ # object.extend(mod) bust the constant cache only if `mod` contains constants of its own.
154
+ # @api private
155
+ module Extension
156
+
157
+ include Enumerable
158
+ include UnsafeEnumerableMethods
159
+
160
+ attr_accessor :pager
161
+
162
+ def last_page?
163
+ if @last_page.nil?
164
+ @last_page = !@pager.truncated?(self)
165
+ end
166
+ @last_page
167
+ end
168
+
169
+ def next_page?
170
+ !last_page?
171
+ end
172
+
173
+ def next_page(params = {})
174
+ if last_page?
175
+ raise LastPageError.new(self)
176
+ else
177
+ next_response(params)
178
+ end
179
+ end
180
+
181
+ def each(&block)
182
+ return enum_for(:each_page) unless block_given?
183
+ response = self
184
+ yield(response)
185
+ until response.last_page?
186
+ response = response.next_page
187
+ yield(response)
188
+ end
189
+ end
190
+ alias each_page each
191
+
192
+ private
193
+
194
+ def next_response(params)
195
+ params = next_page_params(params)
196
+ request = context.client.build_request(context.operation_name, params)
197
+ request.send_request
198
+ end
199
+
200
+ def next_page_params(params)
201
+ # Remove all previous tokens from original params
202
+ # Sometimes a token can be nil and merge would not include it.
203
+ tokens = @pager.tokens.values.map(&:to_sym)
204
+
205
+ params_without_tokens = context[:original_params].reject { |k, _v| tokens.include?(k) }
206
+ params_without_tokens.merge!(@pager.next_tokens(self).merge(params))
207
+ params_without_tokens
208
+ end
209
+
210
+ end
165
211
  end
166
212
  end
@@ -18,6 +18,9 @@ module Aws
18
18
  # @return [Symbol, nil]
19
19
  attr_reader :limit_key
20
20
 
21
+ # @return [Hash, nil]
22
+ attr_reader :tokens
23
+
21
24
  # @param [Seahorse::Client::Response] response
22
25
  # @return [Hash]
23
26
  def next_tokens(response)
@@ -70,6 +70,14 @@ module Aws
70
70
  end
71
71
  end
72
72
 
73
+ if @validate_required && shape.union
74
+ if values.length > 1
75
+ errors << "multiple values provided to union at #{context} - must contain exactly one of the supported types: #{shape.member_names.join(', ')}"
76
+ elsif values.length == 0
77
+ errors << "No values provided to union at #{context} - must contain exactly one of the supported types: #{shape.member_names.join(', ')}"
78
+ end
79
+ end
80
+
73
81
  # validate non-nil members
74
82
  values.each_pair do |name, value|
75
83
  unless value.nil?
@@ -117,11 +125,32 @@ module Aws
117
125
  end
118
126
  end
119
127
 
128
+ def document(ref, value, errors, context)
129
+ document_types = [Hash, Array, Numeric, String, TrueClass, FalseClass, NilClass]
130
+ unless document_types.any? { |t| value.is_a?(t) }
131
+ errors << expected_got(context, "one of #{document_types.join(', ')}", value)
132
+ end
133
+
134
+ # recursively validate types for aggregated types
135
+ case value
136
+ when Hash
137
+ value.each do |k, v|
138
+ document(ref, v, errors, context + "[#{k}]")
139
+ end
140
+ when Array
141
+ value.each do |v|
142
+ document(ref, v, errors, context)
143
+ end
144
+ end
145
+
146
+ end
147
+
120
148
  def shape(ref, value, errors, context)
121
149
  case ref.shape
122
150
  when StructureShape then structure(ref, value, errors, context)
123
151
  when ListShape then list(ref, value, errors, context)
124
152
  when MapShape then map(ref, value, errors, context)
153
+ when DocumentShape then document(ref, value, errors, context)
125
154
  when StringShape
126
155
  unless value.is_a?(String)
127
156
  errors << expected_got(context, "a String", value)