aws-sdk-core 3.191.0 → 3.199.0

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.
Files changed (101) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +153 -1
  3. data/VERSION +1 -1
  4. data/lib/aws-sdk-core/binary/decode_handler.rb +3 -9
  5. data/lib/aws-sdk-core/binary/encode_handler.rb +1 -1
  6. data/lib/aws-sdk-core/binary/event_builder.rb +34 -37
  7. data/lib/aws-sdk-core/binary/event_stream_decoder.rb +1 -0
  8. data/lib/aws-sdk-core/binary/event_stream_encoder.rb +4 -3
  9. data/lib/aws-sdk-core/cbor/cbor_engine.rb +19 -0
  10. data/lib/aws-sdk-core/cbor/decoder.rb +310 -0
  11. data/lib/aws-sdk-core/cbor/encoder.rb +243 -0
  12. data/lib/aws-sdk-core/cbor.rb +106 -0
  13. data/lib/aws-sdk-core/client_stubs.rb +3 -2
  14. data/lib/aws-sdk-core/credential_provider.rb +1 -1
  15. data/lib/aws-sdk-core/ec2_metadata.rb +1 -1
  16. data/lib/aws-sdk-core/ecs_credentials.rb +2 -1
  17. data/lib/aws-sdk-core/endpoints/matchers.rb +5 -1
  18. data/lib/aws-sdk-core/error_handler.rb +41 -0
  19. data/lib/aws-sdk-core/event_emitter.rb +0 -16
  20. data/lib/aws-sdk-core/instance_profile_credentials.rb +3 -2
  21. data/lib/aws-sdk-core/json/builder.rb +8 -1
  22. data/lib/aws-sdk-core/json/error_handler.rb +15 -10
  23. data/lib/aws-sdk-core/json/handler.rb +5 -6
  24. data/lib/aws-sdk-core/json/json_engine.rb +3 -1
  25. data/lib/aws-sdk-core/json/oj_engine.rb +7 -1
  26. data/lib/aws-sdk-core/json/parser.rb +6 -1
  27. data/lib/aws-sdk-core/json.rb +43 -14
  28. data/lib/aws-sdk-core/lru_cache.rb +75 -0
  29. data/lib/aws-sdk-core/pageable_response.rb +1 -1
  30. data/lib/aws-sdk-core/param_validator.rb +7 -2
  31. data/lib/aws-sdk-core/plugins/client_metrics_send_plugin.rb +14 -2
  32. data/lib/aws-sdk-core/plugins/global_configuration.rb +8 -9
  33. data/lib/aws-sdk-core/plugins/invocation_id.rb +1 -11
  34. data/lib/aws-sdk-core/plugins/protocols/api_gateway.rb +3 -1
  35. data/lib/aws-sdk-core/plugins/protocols/ec2.rb +2 -24
  36. data/lib/aws-sdk-core/plugins/protocols/json_rpc.rb +6 -8
  37. data/lib/aws-sdk-core/plugins/protocols/query.rb +4 -2
  38. data/lib/aws-sdk-core/plugins/protocols/rest_json.rb +3 -15
  39. data/lib/aws-sdk-core/plugins/protocols/rest_xml.rb +3 -0
  40. data/lib/aws-sdk-core/plugins/protocols/rpc_v2.rb +17 -0
  41. data/lib/aws-sdk-core/plugins/request_compression.rb +11 -2
  42. data/lib/aws-sdk-core/plugins/retry_errors.rb +10 -3
  43. data/lib/aws-sdk-core/plugins/sign.rb +8 -3
  44. data/lib/aws-sdk-core/plugins/user_agent.rb +61 -26
  45. data/lib/aws-sdk-core/process_credentials.rb +45 -27
  46. data/lib/aws-sdk-core/query/ec2_handler.rb +27 -0
  47. data/lib/aws-sdk-core/query/ec2_param_builder.rb +5 -7
  48. data/lib/aws-sdk-core/query/handler.rb +4 -4
  49. data/lib/aws-sdk-core/query/param_builder.rb +2 -2
  50. data/lib/aws-sdk-core/query.rb +2 -1
  51. data/lib/aws-sdk-core/rest/content_type_handler.rb +60 -0
  52. data/lib/aws-sdk-core/rest/handler.rb +3 -4
  53. data/lib/aws-sdk-core/rest/request/body.rb +32 -5
  54. data/lib/aws-sdk-core/rest/request/endpoint.rb +24 -4
  55. data/lib/aws-sdk-core/rest/request/headers.rb +15 -7
  56. data/lib/aws-sdk-core/rest/request/querystring_builder.rb +23 -11
  57. data/lib/aws-sdk-core/rest/response/body.rb +15 -1
  58. data/lib/aws-sdk-core/rest/response/header_list_parser.rb +79 -0
  59. data/lib/aws-sdk-core/rest/response/headers.rb +8 -3
  60. data/lib/aws-sdk-core/rest.rb +1 -0
  61. data/lib/aws-sdk-core/rpc_v2/builder.rb +62 -0
  62. data/lib/aws-sdk-core/rpc_v2/content_type_handler.rb +45 -0
  63. data/lib/aws-sdk-core/rpc_v2/error_handler.rb +84 -0
  64. data/lib/aws-sdk-core/rpc_v2/handler.rb +74 -0
  65. data/lib/aws-sdk-core/rpc_v2/parser.rb +90 -0
  66. data/lib/aws-sdk-core/rpc_v2.rb +6 -0
  67. data/lib/aws-sdk-core/stubbing/protocols/rpc_v2.rb +41 -0
  68. data/lib/aws-sdk-core/util.rb +39 -0
  69. data/lib/aws-sdk-core/waiters/poller.rb +1 -1
  70. data/lib/aws-sdk-core/xml/builder.rb +17 -9
  71. data/lib/aws-sdk-core/xml/error_handler.rb +32 -42
  72. data/lib/aws-sdk-core/xml/parser/frame.rb +4 -20
  73. data/lib/aws-sdk-core/xml/parser/stack.rb +2 -0
  74. data/lib/aws-sdk-core/xml/parser.rb +2 -6
  75. data/lib/aws-sdk-core.rb +7 -2
  76. data/lib/aws-sdk-sso/client.rb +77 -49
  77. data/lib/aws-sdk-sso.rb +1 -1
  78. data/lib/aws-sdk-ssooidc/client.rb +127 -51
  79. data/lib/aws-sdk-ssooidc/client_api.rb +22 -0
  80. data/lib/aws-sdk-ssooidc/errors.rb +21 -0
  81. data/lib/aws-sdk-ssooidc/types.rb +77 -9
  82. data/lib/aws-sdk-ssooidc.rb +1 -1
  83. data/lib/aws-sdk-sts/client.rb +77 -49
  84. data/lib/aws-sdk-sts/client_api.rb +8 -8
  85. data/lib/aws-sdk-sts.rb +1 -1
  86. data/lib/seahorse/client/async_base.rb +1 -1
  87. data/lib/seahorse/client/async_response.rb +19 -0
  88. data/lib/seahorse/client/base.rb +18 -7
  89. data/lib/seahorse/client/h2/handler.rb +1 -0
  90. data/lib/seahorse/client/handler.rb +1 -1
  91. data/lib/seahorse/client/net_http/connection_pool.rb +3 -9
  92. data/lib/seahorse/client/plugin.rb +8 -0
  93. data/lib/seahorse/client/plugins/endpoint.rb +0 -1
  94. data/lib/seahorse/client/plugins/net_http.rb +48 -16
  95. data/lib/seahorse/model/shapes.rb +2 -2
  96. metadata +24 -7
  97. /data/lib/aws-sdk-core/xml/parser/{engines/libxml.rb → libxml_engine.rb} +0 -0
  98. /data/lib/aws-sdk-core/xml/parser/{engines/nokogiri.rb → nokogiri_engine.rb} +0 -0
  99. /data/lib/aws-sdk-core/xml/parser/{engines/oga.rb → oga_engine.rb} +0 -0
  100. /data/lib/aws-sdk-core/xml/parser/{engines/ox.rb → ox_engine.rb} +0 -0
  101. /data/lib/aws-sdk-core/xml/parser/{engines/rexml.rb → rexml_engine.rb} +0 -0
@@ -4,6 +4,23 @@ module Aws
4
4
  module Plugins
5
5
  # @api private
6
6
  class UserAgent < Seahorse::Client::Plugin
7
+ METRICS = Aws::Json.load(<<-METRICS)
8
+ {
9
+ "RESOURCE_MODEL": "A",
10
+ "WAITER": "B",
11
+ "PAGINATOR": "C",
12
+ "RETRY_MODE_LEGACY": "D",
13
+ "RETRY_MODE_STANDARD": "E",
14
+ "RETRY_MODE_ADAPTIVE": "F",
15
+ "S3_TRANSFER": "G",
16
+ "S3_CRYPTO_V1N": "H",
17
+ "S3_CRYPTO_V2": "I",
18
+ "S3_EXPRESS_BUCKET": "J",
19
+ "S3_ACCESS_GRANTS": "K",
20
+ "GZIP_REQUEST_COMPRESSION": "L"
21
+ }
22
+ METRICS
23
+
7
24
  # @api private
8
25
  option(:user_agent_suffix)
9
26
  # @api private
@@ -14,20 +31,29 @@ module Aws
14
31
  doc_type: 'String',
15
32
  docstring: <<-DOCS) do |cfg|
16
33
  A unique and opaque application ID that is appended to the
17
- User-Agent header as app/<sdk_ua_app_id>. It should have a
18
- maximum length of 50.
34
+ User-Agent header as app/sdk_ua_app_id. It should have a
35
+ maximum length of 50. This variable is sourced from environment
36
+ variable AWS_SDK_UA_APP_ID or the shared config profile attribute sdk_ua_app_id.
19
37
  DOCS
20
38
  app_id = ENV['AWS_SDK_UA_APP_ID']
21
39
  app_id ||= Aws.shared_config.sdk_ua_app_id(profile: cfg.profile)
22
40
  app_id
23
41
  end
24
42
 
25
- def self.feature(feature, &block)
26
- Thread.current[:aws_sdk_core_user_agent_feature] ||= []
27
- Thread.current[:aws_sdk_core_user_agent_feature] << "ft/#{feature}"
43
+ # Deprecated - must exist for old service gems
44
+ def self.feature(_feature, &block)
45
+ block.call
46
+ end
47
+
48
+ def self.metric(metric, &block)
49
+ Thread.current[:aws_sdk_core_user_agent_metric] ||= []
50
+ Thread.current[:aws_sdk_core_user_agent_metric] << METRICS[metric]
28
51
  block.call
29
52
  ensure
30
- Thread.current[:aws_sdk_core_user_agent_feature].pop
53
+ Thread.current[:aws_sdk_core_user_agent_metric].pop
54
+ if Thread.current[:aws_sdk_core_user_agent_metric].empty?
55
+ Thread.current[:aws_sdk_core_user_agent_metric] = nil
56
+ end
31
57
  end
32
58
 
33
59
  # @api private
@@ -48,15 +74,24 @@ maximum length of 50.
48
74
 
49
75
  def to_s
50
76
  ua = "aws-sdk-ruby3/#{CORE_GEM_VERSION}"
51
- ua += ' ua/2.0'
52
- ua += " #{api_metadata}" if api_metadata
77
+ ua += ' ua/2.1'
78
+ if (api_m = api_metadata)
79
+ ua += " #{api_m}"
80
+ end
53
81
  ua += " #{os_metadata}"
54
82
  ua += " #{language_metadata}"
55
- ua += " #{env_metadata}" if env_metadata
56
- ua += " #{config_metadata}" if config_metadata
57
- ua += " #{app_id}" if app_id
58
- ua += " #{feature_metadata}" if feature_metadata
59
- ua += " #{framework_metadata}" if framework_metadata
83
+ if (env_m = env_metadata)
84
+ ua += " #{env_m}"
85
+ end
86
+ if (app_id_m = app_id_metadata)
87
+ ua += " #{app_id_m}"
88
+ end
89
+ if (framework_m = framework_metadata)
90
+ ua += " #{framework_m}"
91
+ end
92
+ if (metric_m = metric_metadata)
93
+ ua += " #{metric_m}"
94
+ end
60
95
  if @context.config.user_agent_suffix
61
96
  ua += " #{@context.config.user_agent_suffix}"
62
97
  end
@@ -92,7 +127,6 @@ maximum length of 50.
92
127
  local_version = Gem::Platform.local.version
93
128
  metadata += "##{local_version}" if local_version
94
129
  metadata += " md/#{RbConfig::CONFIG['host_cpu']}"
95
- metadata
96
130
  end
97
131
 
98
132
  # Used to be RUBY_ENGINE/RUBY_VERSION
@@ -106,11 +140,7 @@ maximum length of 50.
106
140
  "exec-env/#{execution_env}"
107
141
  end
108
142
 
109
- def config_metadata
110
- "cfg/retry-mode##{@context.config.retry_mode}"
111
- end
112
-
113
- def app_id
143
+ def app_id_metadata
114
144
  return unless (app_id = @context.config.sdk_ua_app_id)
115
145
 
116
146
  # Sanitize and only allow these characters
@@ -118,12 +148,6 @@ maximum length of 50.
118
148
  "app/#{app_id}"
119
149
  end
120
150
 
121
- def feature_metadata
122
- return unless Thread.current[:aws_sdk_core_user_agent_feature]
123
-
124
- Thread.current[:aws_sdk_core_user_agent_feature].join(' ')
125
- end
126
-
127
151
  def framework_metadata
128
152
  if (frameworks_cfg = @context.config.user_agent_frameworks).empty?
129
153
  return
@@ -140,10 +164,21 @@ maximum length of 50.
140
164
  end
141
165
  frameworks.map { |n, v| "lib/#{n}##{v}" }.join(' ')
142
166
  end
167
+
168
+ def metric_metadata
169
+ return unless Thread.current[:aws_sdk_core_user_agent_metric]
170
+
171
+ metrics = Thread.current[:aws_sdk_core_user_agent_metric].join(',')
172
+ # Metric metadata is limited to 1024 bytes
173
+ return "m/#{metrics}" if metrics.bytesize <= 1024
174
+
175
+ # Removes the last unfinished metric
176
+ "m/#{metrics[0...metrics[0..1024].rindex(',')]}"
177
+ end
143
178
  end
144
179
  end
145
180
 
146
- handler(Handler, priority: 1)
181
+ handler(Handler, step: :sign, priority: 97)
147
182
  end
148
183
  end
149
184
  end
@@ -2,9 +2,15 @@
2
2
 
3
3
  module Aws
4
4
  # A credential provider that executes a given process and attempts
5
- # to read its stdout to recieve a JSON payload containing the credentials.
5
+ # to read its stdout to receive a JSON payload containing the credentials.
6
6
  #
7
- # credentials = Aws::ProcessCredentials.new('/usr/bin/credential_proc')
7
+ # credentials = Aws::ProcessCredentials.new(['/usr/bin/credential_proc'])
8
+ # ec2 = Aws::EC2::Client.new(credentials: credentials)
9
+ #
10
+ # Arguments should be provided as strings in the array, for example:
11
+ #
12
+ # process = ['/usr/bin/credential_proc', 'arg1', 'arg2']
13
+ # credentials = Aws::ProcessCredentials.new(process)
8
14
  # ec2 = Aws::EC2::Client.new(credentials: credentials)
9
15
  #
10
16
  # Automatically handles refreshing credentials if an Expiration time is
@@ -19,40 +25,49 @@ module Aws
19
25
  # Creates a new ProcessCredentials object, which allows an
20
26
  # external process to be used as a credential provider.
21
27
  #
22
- # @param [String] process Invocation string for process
23
- # credentials provider.
28
+ # @param [Array<String>, String] process An array of strings including
29
+ # the process name and its arguments to execute, or a single string to be
30
+ # executed by the shell (deprecated and insecure).
24
31
  def initialize(process)
32
+ if process.is_a?(String)
33
+ warn('Passing a single string to Aws::ProcessCredentials.new '\
34
+ 'is insecure, please use use an array of system arguments instead')
35
+ end
25
36
  @process = process
26
- @credentials = credentials_from_process(@process)
37
+ @credentials = credentials_from_process
27
38
  @async_refresh = false
28
39
 
29
40
  super
30
41
  end
31
42
 
32
43
  private
33
- def credentials_from_process(proc_invocation)
34
- begin
35
- raw_out = `#{proc_invocation}`
36
- process_status = $?
37
- rescue Errno::ENOENT
38
- raise Errors::InvalidProcessCredentialsPayload.new("Could not find process #{proc_invocation}")
44
+
45
+ def credentials_from_process
46
+ r, w = IO.pipe
47
+ success = system(*@process, out: w)
48
+ w.close
49
+ raw_out = r.read
50
+ r.close
51
+
52
+ unless success
53
+ raise Errors::InvalidProcessCredentialsPayload.new(
54
+ 'credential_process provider failure, the credential process had '\
55
+ 'non zero exit status and failed to provide credentials'
56
+ )
39
57
  end
40
58
 
41
- if process_status.success?
42
- begin
43
- creds_json = Aws::Json.load(raw_out)
44
- rescue Aws::Json::ParseError
45
- raise Errors::InvalidProcessCredentialsPayload.new("Invalid JSON response")
46
- end
47
- payload_version = creds_json['Version']
48
- if payload_version == 1
49
- _parse_payload_format_v1(creds_json)
50
- else
51
- raise Errors::InvalidProcessCredentialsPayload.new("Invalid version #{payload_version} for credentials payload")
52
- end
53
- else
54
- raise Errors::InvalidProcessCredentialsPayload.new('credential_process provider failure, the credential process had non zero exit status and failed to provide credentials')
59
+ begin
60
+ creds_json = Aws::Json.load(raw_out)
61
+ rescue Aws::Json::ParseError
62
+ raise Errors::InvalidProcessCredentialsPayload.new('Invalid JSON response')
55
63
  end
64
+
65
+ payload_version = creds_json['Version']
66
+ return _parse_payload_format_v1(creds_json) if payload_version == 1
67
+
68
+ raise Errors::InvalidProcessCredentialsPayload.new(
69
+ "Invalid version #{payload_version} for credentials payload"
70
+ )
56
71
  end
57
72
 
58
73
  def _parse_payload_format_v1(creds_json)
@@ -64,11 +79,14 @@ module Aws
64
79
 
65
80
  @expiration = creds_json['Expiration'] ? Time.iso8601(creds_json['Expiration']) : nil
66
81
  return creds if creds.set?
67
- raise Errors::InvalidProcessCredentialsPayload.new("Invalid payload for JSON credentials version 1")
82
+
83
+ raise Errors::InvalidProcessCredentialsPayload.new(
84
+ 'Invalid payload for JSON credentials version 1'
85
+ )
68
86
  end
69
87
 
70
88
  def refresh
71
- @credentials = credentials_from_process(@process)
89
+ @credentials = credentials_from_process
72
90
  end
73
91
 
74
92
  def near_expiration?(expiration_length)
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aws
4
+ # @api private
5
+ module Query
6
+ class EC2Handler < Aws::Query::Handler
7
+
8
+ def apply_params(param_list, params, rules)
9
+ Aws::Query::EC2ParamBuilder.new(param_list).apply(rules, params)
10
+ end
11
+
12
+ def parse_xml(context)
13
+ if (rules = context.operation.output)
14
+ parser = Xml::Parser.new(rules)
15
+ parser.parse(xml(context)) do |path, value|
16
+ if path.size == 2 && path.last == 'requestId'
17
+ context.metadata[:request_id] = value
18
+ end
19
+ end
20
+ else
21
+ EmptyStructure.new
22
+ end
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -31,13 +31,11 @@ module Aws
31
31
  end
32
32
 
33
33
  def list(ref, values, prefix)
34
- if values.empty?
35
- set(prefix, '')
36
- else
37
- member_ref = ref.shape.member
38
- values.each.with_index do |value, n|
39
- format(member_ref, value, "#{prefix}.#{n+1}")
40
- end
34
+ return if values.empty?
35
+
36
+ member_ref = ref.shape.member
37
+ values.each.with_index do |value, n|
38
+ format(member_ref, value, "#{prefix}.#{n + 1}")
41
39
  end
42
40
  end
43
41
 
@@ -27,13 +27,13 @@ module Aws
27
27
  # @return [Seahorse::Client::Response]
28
28
  def call(context)
29
29
  build_request(context)
30
- @handler.call(context).on_success do |response|
31
- response.error = nil
30
+ @handler.call(context).on_success do |resp|
31
+ resp.error = nil
32
32
  parsed = parse_xml(context)
33
33
  if parsed.nil? || parsed == EmptyStructure
34
- response.data = EmptyStructure.new
34
+ resp.data = EmptyStructure.new
35
35
  else
36
- response.data = parsed
36
+ resp.data = parsed
37
37
  end
38
38
  end
39
39
  end
@@ -36,7 +36,7 @@ module Aws
36
36
  return
37
37
  end
38
38
  if flat?(ref)
39
- if name = query_name(member_ref)
39
+ if (name = query_name(ref))
40
40
  parts = prefix.split('.')
41
41
  parts.pop
42
42
  parts.push(name)
@@ -82,7 +82,7 @@ module Aws
82
82
  end
83
83
 
84
84
  def flat?(ref)
85
- ref.shape.flattened
85
+ ref[:flattened] || ref.shape.flattened
86
86
  end
87
87
 
88
88
  def timestamp(ref, value)
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'query/ec2_param_builder'
4
3
  require_relative 'query/handler'
4
+ require_relative 'query/ec2_handler'
5
5
  require_relative 'query/param'
6
6
  require_relative 'query/param_builder'
7
+ require_relative 'query/ec2_param_builder'
7
8
  require_relative 'query/param_list'
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aws
4
+ module Rest
5
+ # NOTE: headers could be already populated if specified on input shape
6
+ class ContentTypeHandler < Seahorse::Client::Handler
7
+ def call(context)
8
+ if eventstream?(context)
9
+ context.http_request.headers['Content-Type'] ||=
10
+ 'application/vnd.amazon.eventstream'
11
+ elsif (payload = context.operation.input[:payload_member])
12
+ case payload.shape
13
+ when Seahorse::Model::Shapes::BlobShape
14
+ context.http_request.headers['Content-Type'] ||=
15
+ 'application/octet-stream'
16
+ when Seahorse::Model::Shapes::StringShape
17
+ context.http_request.headers['Content-Type'] ||=
18
+ 'text/plain'
19
+ else
20
+ apply_default_content_type(context)
21
+ end
22
+ elsif (body = context.http_request.body) &&
23
+ (!body.respond_to?(:size) || non_empty_body?(body))
24
+ apply_default_content_type(context)
25
+ end
26
+
27
+ @handler.call(context)
28
+ end
29
+
30
+ private
31
+
32
+ def non_empty_body?(body)
33
+ body.respond_to?(:size) && body.size.positive?
34
+ end
35
+
36
+ def eventstream?(context)
37
+ context.operation.input.shape.members.each do |_, ref|
38
+ return true if ref.eventstream
39
+ end
40
+ false
41
+ end
42
+
43
+ # content-type defaults as noted here:
44
+ # rest-json: https://smithy.io/2.0/aws/protocols/aws-restxml-protocol.html#content-type
45
+ # rest-xml: https://smithy.io/2.0/aws/protocols/aws-restxml-protocol.html#content-type
46
+ def apply_default_content_type(context)
47
+ protocol = context.config.api.metadata['protocol']
48
+ case protocol
49
+ when 'rest-json'
50
+ context.http_request.headers['Content-Type'] ||=
51
+ 'application/json'
52
+ when 'rest-xml'
53
+ context.http_request.headers['Content-Type'] ||=
54
+ 'application/xml'
55
+ else raise "Unsupported protocol #{protocol}"
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -7,10 +7,9 @@ module Aws
7
7
 
8
8
  def call(context)
9
9
  Rest::Request::Builder.new.apply(context)
10
- resp = @handler.call(context)
11
- resp.on(200..299) { |response| Response::Parser.new.apply(response) }
12
- resp.on(200..599) { |response| apply_request_id(context) }
13
- resp
10
+ response = @handler.call(context)
11
+ response.on(200..299) { |resp| Response::Parser.new.apply(resp) }
12
+ response.on(200..599) { |_resp| apply_request_id(context) }
14
13
  end
15
14
 
16
15
  private
@@ -18,10 +18,13 @@ module Aws
18
18
  # @param [Hash] params
19
19
  def apply(http_req, params)
20
20
  body = build_body(params)
21
+
21
22
  # for rest-json, ensure we send at least an empty object
22
23
  # don't send an empty object for streaming? case.
23
- if body.nil? && @serializer_class == Json::Builder &&
24
- modeled_body? && !streaming?
24
+ if body.nil? &&
25
+ json_builder? &&
26
+ modeled_body? &&
27
+ !streaming?
25
28
  body = '{}'
26
29
  end
27
30
  http_req.body = body
@@ -45,13 +48,29 @@ module Aws
45
48
  params[@rules[:payload]]
46
49
  elsif @rules[:payload]
47
50
  params = params[@rules[:payload]]
48
- serialize(@rules[:payload_member], params) if params
51
+ if params
52
+ if xml_builder? &&
53
+ @rules.shape.member?(@rules[:payload_member].location_name)
54
+ # serializing payload member name for rest-xml is as follows:
55
+ # 1. Use the member locationName if the member value doesn't match the member's name (default)
56
+ # 2. Use the value of the locationName on the member's target if present
57
+ # 3. Use the shape name of the member's target
58
+ serialize(@rules[:payload_member], params, location_name: payload_location_name)
59
+ else
60
+ serialize(@rules[:payload_member], params)
61
+ end
62
+ end
49
63
  else
50
64
  params = body_params(params)
51
65
  serialize(@rules, params) unless params.empty?
52
66
  end
53
67
  end
54
68
 
69
+ def payload_location_name
70
+ @rules[:payload_member].shape['locationName'] ||
71
+ @rules[:payload_member].shape.name
72
+ end
73
+
55
74
  def streaming?
56
75
  @rules[:payload] && (
57
76
  BlobShape === @rules[:payload_member].shape ||
@@ -59,8 +78,16 @@ module Aws
59
78
  )
60
79
  end
61
80
 
62
- def serialize(rules, params)
63
- @serializer_class.new(rules).serialize(params)
81
+ def xml_builder?
82
+ @serializer_class == Xml::Builder
83
+ end
84
+
85
+ def json_builder?
86
+ @serializer_class == Json::Builder
87
+ end
88
+
89
+ def serialize(rules, params, location_name: nil)
90
+ @serializer_class.new(rules, location_name: location_name).serialize(params)
64
91
  end
65
92
 
66
93
  def body_params(params)
@@ -30,7 +30,9 @@ module Aws
30
30
  private
31
31
 
32
32
  def apply_path_params(uri, params)
33
- path = uri.path.sub(/\/$/, '') + @path_pattern.split('?')[0]
33
+ path = uri.path.sub(%r{/$}, '')
34
+ # handle trailing slash
35
+ path += @path_pattern.split('?')[0] if path.empty? || @path_pattern != '/'
34
36
  uri.path = path.gsub(/{.+?}/) do |placeholder|
35
37
  param_value_for_placeholder(placeholder, params)
36
38
  end
@@ -38,22 +40,40 @@ module Aws
38
40
 
39
41
  def param_value_for_placeholder(placeholder, params)
40
42
  name = param_name(placeholder)
41
- value = params[name].to_s
43
+ param_shape = @rules.shape.member(name).shape
44
+ value =
45
+ case param_shape
46
+ when Seahorse::Model::Shapes::TimestampShape
47
+ timestamp(param_shape, params[name]).to_s
48
+ else
49
+ params[name].to_s
50
+ end
51
+
42
52
  raise ArgumentError, ":#{name} must not be blank" if value.empty?
43
53
 
44
54
  if placeholder.include?('+')
45
- value.gsub(/[^\/]+/) { |v| escape(v) }
55
+ value.gsub(%r{[^/]+}) { |v| escape(v) }
46
56
  else
47
57
  escape(value)
48
58
  end
49
59
  end
50
60
 
51
61
  def param_name(placeholder)
52
- location_name = placeholder.gsub(/[{}+]/,'')
62
+ location_name = placeholder.gsub(/[{}+]/, '')
53
63
  param_name, _ = @rules.shape.member_by_location_name(location_name)
54
64
  param_name
55
65
  end
56
66
 
67
+ def timestamp(ref, value)
68
+ case ref['timestampFormat']
69
+ when 'unixTimestamp' then value.to_i
70
+ when 'rfc822' then value.utc.httpdate
71
+ else
72
+ # serializing as RFC 3399 date-time is the default
73
+ value.utc.iso8601
74
+ end
75
+ end
76
+
57
77
  def apply_querystring_params(uri, params)
58
78
  # collect params that are supposed to be part of the query string
59
79
  parts = @rules.shape.members.inject([]) do |prts, (member_name, member_ref)|
@@ -20,7 +20,8 @@ module Aws
20
20
  def apply(http_req, params)
21
21
  @rules.shape.members.each do |name, ref|
22
22
  value = params[name]
23
- next if value.nil?
23
+ next if value.nil? || ((ref.shape).is_a?(StringShape) && value.empty?)
24
+
24
25
  case ref.location
25
26
  when 'header' then apply_header_value(http_req.headers, ref, value)
26
27
  when 'headers' then apply_header_map(http_req.headers, ref, value)
@@ -49,12 +50,19 @@ module Aws
49
50
  end
50
51
  end
51
52
 
52
- def list(headers, ref, value)
53
- return if !value || value.empty?
54
- headers[ref.location_name] = value
55
- .compact
56
- .map { |s| Seahorse::Util.escape_header_list_string(s.to_s) }
57
- .join(',')
53
+ def list(headers, ref, values)
54
+ return if !values || values.empty?
55
+
56
+ member_ref = ref.shape.member
57
+ values = values.collect do |value|
58
+ case member_ref.shape
59
+ when TimestampShape
60
+ timestamp(member_ref, value).to_s
61
+ else
62
+ Seahorse::Util.escape_header_list_string(value.to_s)
63
+ end
64
+ end
65
+ headers[ref.location_name] = values.compact.join(', ')
58
66
  end
59
67
 
60
68
  def apply_header_map(headers, ref, values)
@@ -30,20 +30,30 @@ module Aws
30
30
  #
31
31
  # @return [String] Returns a built querystring
32
32
  def build(params)
33
+ # keys in query maps must NOT override other keys
34
+ query_keys = query_keys(params)
33
35
  params.map do |(shape_ref, param_value)|
34
- build_part(shape_ref, param_value)
35
- end.join('&')
36
+ build_part(shape_ref, param_value, query_keys)
37
+ end.reject { |p| p.nil? || p.empty? }.join('&')
36
38
  end
37
39
 
38
40
  private
39
41
 
40
- def build_part(shape_ref, param_value)
42
+ def query_keys(params)
43
+ keys = Set.new
44
+ params.each do |(shape_ref, _)|
45
+ keys << shape_ref.location_name unless shape_ref.shape.is_a?(MapShape)
46
+ end
47
+ keys
48
+ end
49
+
50
+ def build_part(shape_ref, param_value, query_keys)
41
51
  case shape_ref.shape
42
52
  # supported scalar types
43
53
  when *SUPPORTED_TYPES
44
54
  "#{shape_ref.location_name}=#{query_value(shape_ref, param_value)}"
45
55
  when MapShape
46
- generate_query_map(shape_ref, param_value)
56
+ generate_query_map(shape_ref, param_value, query_keys)
47
57
  when ListShape
48
58
  generate_query_list(shape_ref, param_value)
49
59
  else
@@ -80,31 +90,33 @@ module Aws
80
90
  end
81
91
  end
82
92
 
83
- def generate_query_map(ref, value)
93
+ def generate_query_map(ref, value, query_keys)
84
94
  case ref.shape.value.shape
85
95
  when StringShape
86
- query_map_of_string(value)
96
+ query_map_of_string(value, query_keys)
87
97
  when ListShape
88
- query_map_of_string_list(value)
98
+ query_map_of_string_list(value, query_keys)
89
99
  else
90
100
  msg = 'Only map of string and string list supported'
91
101
  raise NotImplementedError, msg
92
102
  end
93
103
  end
94
104
 
95
- def query_map_of_string(hash)
105
+ def query_map_of_string(hash, query_keys)
96
106
  list = []
97
107
  hash.each_pair do |key, value|
98
- list << "#{escape(key)}=#{escape(value)}"
108
+ key = escape(key)
109
+ list << "#{key}=#{escape(value)}" unless query_keys.include?(key)
99
110
  end
100
111
  list
101
112
  end
102
113
 
103
- def query_map_of_string_list(hash)
114
+ def query_map_of_string_list(hash, query_keys)
104
115
  list = []
105
116
  hash.each_pair do |key, values|
117
+ key = escape(key)
106
118
  values.each do |value|
107
- list << "#{escape(key)}=#{escape(value)}"
119
+ list << "#{key}=#{escape(value)}" unless query_keys.include?(key)
108
120
  end
109
121
  end
110
122
  list