aws-sdk-core 3.196.1 → 3.199.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +39 -0
  3. data/VERSION +1 -1
  4. data/lib/aws-sdk-core/binary/decode_handler.rb +3 -4
  5. data/lib/aws-sdk-core/binary/encode_handler.rb +1 -1
  6. data/lib/aws-sdk-core/binary/event_stream_decoder.rb +1 -0
  7. data/lib/aws-sdk-core/binary/event_stream_encoder.rb +4 -3
  8. data/lib/aws-sdk-core/cbor/cbor_engine.rb +19 -0
  9. data/lib/aws-sdk-core/cbor/decoder.rb +310 -0
  10. data/lib/aws-sdk-core/cbor/encoder.rb +243 -0
  11. data/lib/aws-sdk-core/cbor.rb +106 -0
  12. data/lib/aws-sdk-core/client_stubs.rb +3 -2
  13. data/lib/aws-sdk-core/endpoints/matchers.rb +5 -1
  14. data/lib/aws-sdk-core/error_handler.rb +41 -0
  15. data/lib/aws-sdk-core/json/error_handler.rb +6 -8
  16. data/lib/aws-sdk-core/json/handler.rb +5 -6
  17. data/lib/aws-sdk-core/json/json_engine.rb +3 -1
  18. data/lib/aws-sdk-core/json/oj_engine.rb +7 -1
  19. data/lib/aws-sdk-core/json/parser.rb +2 -0
  20. data/lib/aws-sdk-core/json.rb +43 -14
  21. data/lib/aws-sdk-core/pageable_response.rb +1 -1
  22. data/lib/aws-sdk-core/plugins/client_metrics_send_plugin.rb +14 -2
  23. data/lib/aws-sdk-core/plugins/global_configuration.rb +8 -9
  24. data/lib/aws-sdk-core/plugins/protocols/api_gateway.rb +3 -1
  25. data/lib/aws-sdk-core/plugins/protocols/ec2.rb +2 -24
  26. data/lib/aws-sdk-core/plugins/protocols/json_rpc.rb +6 -8
  27. data/lib/aws-sdk-core/plugins/protocols/query.rb +4 -2
  28. data/lib/aws-sdk-core/plugins/protocols/rest_json.rb +4 -3
  29. data/lib/aws-sdk-core/plugins/protocols/rest_xml.rb +5 -1
  30. data/lib/aws-sdk-core/plugins/protocols/rpc_v2.rb +17 -0
  31. data/lib/aws-sdk-core/plugins/request_compression.rb +10 -1
  32. data/lib/aws-sdk-core/plugins/retry_errors.rb +10 -3
  33. data/lib/aws-sdk-core/plugins/user_agent.rb +58 -24
  34. data/lib/aws-sdk-core/process_credentials.rb +45 -27
  35. data/lib/aws-sdk-core/query/ec2_handler.rb +27 -0
  36. data/lib/aws-sdk-core/query/handler.rb +4 -4
  37. data/lib/aws-sdk-core/query.rb +2 -1
  38. data/lib/aws-sdk-core/rest/{request/content_type.rb → content_type_handler.rb} +1 -1
  39. data/lib/aws-sdk-core/rest/handler.rb +3 -4
  40. data/lib/aws-sdk-core/rest/request/endpoint.rb +3 -1
  41. data/lib/aws-sdk-core/rest.rb +1 -1
  42. data/lib/aws-sdk-core/rpc_v2/builder.rb +62 -0
  43. data/lib/aws-sdk-core/rpc_v2/content_type_handler.rb +45 -0
  44. data/lib/aws-sdk-core/rpc_v2/error_handler.rb +84 -0
  45. data/lib/aws-sdk-core/rpc_v2/handler.rb +74 -0
  46. data/lib/aws-sdk-core/rpc_v2/parser.rb +90 -0
  47. data/lib/aws-sdk-core/rpc_v2.rb +6 -0
  48. data/lib/aws-sdk-core/stubbing/protocols/rpc_v2.rb +41 -0
  49. data/lib/aws-sdk-core/util.rb +4 -4
  50. data/lib/aws-sdk-core/waiters/poller.rb +1 -1
  51. data/lib/aws-sdk-core/xml/error_handler.rb +11 -37
  52. data/lib/aws-sdk-core/xml/parser.rb +2 -6
  53. data/lib/aws-sdk-core.rb +6 -2
  54. data/lib/aws-sdk-sso/client.rb +6 -3
  55. data/lib/aws-sdk-sso.rb +1 -1
  56. data/lib/aws-sdk-ssooidc/client.rb +6 -3
  57. data/lib/aws-sdk-ssooidc.rb +1 -1
  58. data/lib/aws-sdk-sts/client.rb +6 -3
  59. data/lib/aws-sdk-sts.rb +1 -1
  60. data/lib/seahorse/client/base.rb +17 -7
  61. data/lib/seahorse/client/handler.rb +1 -1
  62. data/lib/seahorse/client/plugins/endpoint.rb +0 -1
  63. metadata +22 -8
  64. /data/lib/aws-sdk-core/xml/parser/{engines/libxml.rb → libxml_engine.rb} +0 -0
  65. /data/lib/aws-sdk-core/xml/parser/{engines/nokogiri.rb → nokogiri_engine.rb} +0 -0
  66. /data/lib/aws-sdk-core/xml/parser/{engines/oga.rb → oga_engine.rb} +0 -0
  67. /data/lib/aws-sdk-core/xml/parser/{engines/ox.rb → ox_engine.rb} +0 -0
  68. /data/lib/aws-sdk-core/xml/parser/{engines/rexml.rb → rexml_engine.rb} +0 -0
@@ -113,7 +113,6 @@ Specifies which retry algorithm to use. Values are:
113
113
  functionality of `standard` mode along with automatic client side
114
114
  throttling. This is a provisional mode that may change behavior
115
115
  in the future.
116
-
117
116
  DOCS
118
117
  resolve_retry_mode(cfg)
119
118
  end
@@ -235,7 +234,7 @@ a clock skew correction and retry requests with skewed client clocks.
235
234
 
236
235
  get_send_token(config)
237
236
  add_retry_headers(context)
238
- response = @handler.call(context)
237
+ response = with_metric(config.retry_mode) { @handler.call(context) }
239
238
  error_inspector = Retries::ErrorInspector.new(
240
239
  response.error, response.context.http_response.status_code
241
240
  )
@@ -272,6 +271,10 @@ a clock skew correction and retry requests with skewed client clocks.
272
271
 
273
272
  private
274
273
 
274
+ def with_metric(retry_mode, &block)
275
+ Aws::Plugins::UserAgent.metric("RETRY_MODE_#{retry_mode.upcase}", &block)
276
+ end
277
+
275
278
  def get_send_token(config)
276
279
  # either fail fast or block until a token becomes available
277
280
  # must be configurable
@@ -359,7 +362,7 @@ a clock skew correction and retry requests with skewed client clocks.
359
362
  class LegacyHandler < Seahorse::Client::Handler
360
363
 
361
364
  def call(context)
362
- response = @handler.call(context)
365
+ response = with_metric { @handler.call(context) }
363
366
  if response.error
364
367
  error_inspector = Retries::ErrorInspector.new(
365
368
  response.error, response.context.http_response.status_code
@@ -378,6 +381,10 @@ a clock skew correction and retry requests with skewed client clocks.
378
381
 
379
382
  private
380
383
 
384
+ def with_metric(&block)
385
+ Aws::Plugins::UserAgent.metric('RETRY_MODE_LEGACY', &block)
386
+ end
387
+
381
388
  def retry_if_possible(response, error_inspector)
382
389
  context = response.context
383
390
  if should_retry?(context, error_inspector)
@@ -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
@@ -23,12 +40,20 @@ variable AWS_SDK_UA_APP_ID or the shared config profile attribute sdk_ua_app_id.
23
40
  app_id
24
41
  end
25
42
 
26
- def self.feature(feature, &block)
27
- Thread.current[:aws_sdk_core_user_agent_feature] ||= []
28
- 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]
29
51
  block.call
30
52
  ensure
31
- 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
32
57
  end
33
58
 
34
59
  # @api private
@@ -49,15 +74,24 @@ variable AWS_SDK_UA_APP_ID or the shared config profile attribute sdk_ua_app_id.
49
74
 
50
75
  def to_s
51
76
  ua = "aws-sdk-ruby3/#{CORE_GEM_VERSION}"
52
- ua += ' ua/2.0'
53
- ua += " #{api_metadata}" if api_metadata
77
+ ua += ' ua/2.1'
78
+ if (api_m = api_metadata)
79
+ ua += " #{api_m}"
80
+ end
54
81
  ua += " #{os_metadata}"
55
82
  ua += " #{language_metadata}"
56
- ua += " #{env_metadata}" if env_metadata
57
- ua += " #{config_metadata}" if config_metadata
58
- ua += " #{app_id}" if app_id
59
- ua += " #{feature_metadata}" if feature_metadata
60
- 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
61
95
  if @context.config.user_agent_suffix
62
96
  ua += " #{@context.config.user_agent_suffix}"
63
97
  end
@@ -93,7 +127,6 @@ variable AWS_SDK_UA_APP_ID or the shared config profile attribute sdk_ua_app_id.
93
127
  local_version = Gem::Platform.local.version
94
128
  metadata += "##{local_version}" if local_version
95
129
  metadata += " md/#{RbConfig::CONFIG['host_cpu']}"
96
- metadata
97
130
  end
98
131
 
99
132
  # Used to be RUBY_ENGINE/RUBY_VERSION
@@ -107,11 +140,7 @@ variable AWS_SDK_UA_APP_ID or the shared config profile attribute sdk_ua_app_id.
107
140
  "exec-env/#{execution_env}"
108
141
  end
109
142
 
110
- def config_metadata
111
- "cfg/retry-mode##{@context.config.retry_mode}"
112
- end
113
-
114
- def app_id
143
+ def app_id_metadata
115
144
  return unless (app_id = @context.config.sdk_ua_app_id)
116
145
 
117
146
  # Sanitize and only allow these characters
@@ -119,12 +148,6 @@ variable AWS_SDK_UA_APP_ID or the shared config profile attribute sdk_ua_app_id.
119
148
  "app/#{app_id}"
120
149
  end
121
150
 
122
- def feature_metadata
123
- return unless Thread.current[:aws_sdk_core_user_agent_feature]
124
-
125
- Thread.current[:aws_sdk_core_user_agent_feature].join(' ')
126
- end
127
-
128
151
  def framework_metadata
129
152
  if (frameworks_cfg = @context.config.user_agent_frameworks).empty?
130
153
  return
@@ -141,10 +164,21 @@ variable AWS_SDK_UA_APP_ID or the shared config profile attribute sdk_ua_app_id.
141
164
  end
142
165
  frameworks.map { |n, v| "lib/#{n}##{v}" }.join(' ')
143
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
144
178
  end
145
179
  end
146
180
 
147
- handler(Handler, priority: 1)
181
+ handler(Handler, step: :sign, priority: 97)
148
182
  end
149
183
  end
150
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
@@ -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
@@ -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'
@@ -35,7 +35,7 @@ module Aws
35
35
 
36
36
  def eventstream?(context)
37
37
  context.operation.input.shape.members.each do |_, ref|
38
- return ref if ref.eventstream
38
+ return true if ref.eventstream
39
39
  end
40
40
  false
41
41
  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
@@ -30,7 +30,9 @@ module Aws
30
30
  private
31
31
 
32
32
  def apply_path_params(uri, params)
33
- path = uri.path.sub(%r{/$}, '') + @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
@@ -1,12 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'rest/handler'
4
+ require_relative 'rest/content_type_handler'
4
5
  require_relative 'rest/request/body'
5
6
  require_relative 'rest/request/builder'
6
7
  require_relative 'rest/request/endpoint'
7
8
  require_relative 'rest/request/headers'
8
9
  require_relative 'rest/request/querystring_builder'
9
- require_relative 'rest/request/content_type'
10
10
  require_relative 'rest/response/body'
11
11
  require_relative 'rest/response/headers'
12
12
  require_relative 'rest/response/parser'
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'base64'
4
+
5
+ module Aws
6
+ module RpcV2
7
+ class Builder
8
+ include Seahorse::Model::Shapes
9
+
10
+ def initialize(rules, _options = {})
11
+ @rules = rules
12
+ end
13
+
14
+ def serialize(params)
15
+ # If the input shape is empty, do not set a body. This is
16
+ # different than if the input shape is a structure with no members.
17
+ return nil if @rules.shape.struct_class == EmptyStructure
18
+
19
+ Cbor.encode(format(@rules, params))
20
+ end
21
+
22
+ private
23
+
24
+ def structure(ref, values)
25
+ shape = ref.shape
26
+ values.each_pair.with_object({}) do |(key, value), data|
27
+ if shape.member?(key) && !value.nil?
28
+ member_ref = shape.member(key)
29
+ member_name = member_ref.location_name || key
30
+ data[member_name] = format(member_ref, value)
31
+ end
32
+ end
33
+ end
34
+
35
+ def list(ref, values)
36
+ member_ref = ref.shape.member
37
+ values.collect { |value| format(member_ref, value) }
38
+ end
39
+
40
+ def map(ref, values)
41
+ value_ref = ref.shape.value
42
+ values.each.with_object({}) do |(key, value), data|
43
+ data[key] = format(value_ref, value)
44
+ end
45
+ end
46
+
47
+ def blob(value)
48
+ (String === value ? value : value.read).force_encoding(Encoding::BINARY)
49
+ end
50
+
51
+ def format(ref, value)
52
+ case ref.shape
53
+ when StructureShape then structure(ref, value)
54
+ when ListShape then list(ref, value)
55
+ when MapShape then map(ref, value)
56
+ when BlobShape then blob(value)
57
+ else value
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aws
4
+ module RpcV2
5
+ class ContentTypeHandler < Seahorse::Client::Handler
6
+ def call(context)
7
+ content_type =
8
+ if eventstream_input?(context)
9
+ 'application/vnd.amazon.eventstream'
10
+ elsif !empty_input_structure?(context)
11
+ 'application/cbor'
12
+ end
13
+ accept =
14
+ if eventstream_output?(context)
15
+ 'application/vnd.amazon.eventstream'
16
+ end
17
+
18
+ headers = context.http_request.headers
19
+ headers['Content-Type'] ||= content_type if content_type
20
+ headers['Accept'] ||= accept if accept
21
+ @handler.call(context)
22
+ end
23
+
24
+ private
25
+
26
+ def eventstream_input?(context)
27
+ context.operation.input.shape.members.each do |_, ref|
28
+ return true if ref.eventstream
29
+ end
30
+ false
31
+ end
32
+
33
+ def eventstream_output?(context)
34
+ context.operation.output.shape.members.each do |_, ref|
35
+ return true if ref.eventstream
36
+ end
37
+ false
38
+ end
39
+
40
+ def empty_input_structure?(context)
41
+ context.operation.input.shape.struct_class == EmptyStructure
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aws
4
+ module RpcV2
5
+ class ErrorHandler < Aws::ErrorHandler
6
+
7
+ def call(context)
8
+ # Malformed responses should throw an http based error, so we check
9
+ # 200 range for error handling only for this case.
10
+ @handler.call(context).on(200..599) do |response|
11
+ if !valid_response?(context)
12
+ code, message, data = http_status_error(context)
13
+ response.error = build_error(context, code, message, data)
14
+ elsif (300..599).cover?(context.http_response.status_code)
15
+ response.error = error(context)
16
+ end
17
+ response.data = nil
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def valid_response?(context)
24
+ req_header = context.http_request.headers['smithy-protocol']
25
+ resp_header = context.http_response.headers['smithy-protocol']
26
+ req_header == resp_header
27
+ end
28
+
29
+ def extract_error(body, context)
30
+ data = Cbor.decode(body)
31
+ code = error_code(data, context)
32
+ message = data['message']
33
+ data = parse_error_data(context, body, code)
34
+ [code, message, data]
35
+ rescue Cbor::Error
36
+ [http_status_error_code(context), '', EmptyStructure.new]
37
+ end
38
+
39
+ def error_code(data, context)
40
+ code =
41
+ if aws_query_error?(context)
42
+ error = context.http_response.headers['x-amzn-query-error'].split(';')[0]
43
+ remove_prefix(error, context)
44
+ else
45
+ data['__type']
46
+ end
47
+ if code
48
+ code.split('#').last
49
+ else
50
+ http_status_error_code(context)
51
+ end
52
+ end
53
+
54
+ def parse_error_data(context, body, code)
55
+ data = EmptyStructure.new
56
+ if (error_rules = context.operation.errors)
57
+ error_rules.each do |rule|
58
+ # match modeled shape name with the type(code) only
59
+ # some type(code) might contains invalid characters
60
+ # such as ':' (efs) etc
61
+ match = rule.shape.name == code.gsub(/[^^a-zA-Z0-9]/, '')
62
+ next unless match && rule.shape.members.any?
63
+
64
+ data = Parser.new(rule).parse(body)
65
+ end
66
+ end
67
+ data
68
+ end
69
+
70
+ def aws_query_error?(context)
71
+ context.config.api.metadata['awsQueryCompatible'] &&
72
+ context.http_response.headers['x-amzn-query-error']
73
+ end
74
+
75
+ def remove_prefix(error_code, context)
76
+ if (prefix = context.config.api.metadata['errorPrefix'])
77
+ error_code.sub(/^#{prefix}/, '')
78
+ else
79
+ error_code
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aws
4
+ module RpcV2
5
+ class Handler < Seahorse::Client::Handler
6
+ # @param [Seahorse::Client::RequestContext] context
7
+ # @return [Seahorse::Client::Response]
8
+ def call(context)
9
+ build_request(context)
10
+ response = @handler.call(context)
11
+ response.on(200..299) { |resp| resp.data = parse_body(context) }
12
+ response.on(200..599) { |_resp| apply_request_id(context) }
13
+ response
14
+ end
15
+
16
+ private
17
+
18
+ def build_request(context)
19
+ context.http_request.headers['smithy-protocol'] = 'rpc-v2-cbor'
20
+ context.http_request.http_method = 'POST'
21
+ context.http_request.body = build_body(context)
22
+ build_url(context)
23
+ end
24
+
25
+ def build_url(context)
26
+ base = context.http_request.endpoint
27
+ service_name = context.config.api.metadata['targetPrefix']
28
+ base.path += "/service/#{service_name}/operation/#{context.operation.name}"
29
+ end
30
+
31
+ def build_body(context)
32
+ Builder.new(context.operation.input).serialize(context.params)
33
+ end
34
+
35
+ def parse_body(context)
36
+ cbor = context.http_response.body_contents
37
+ if (rules = context.operation.output)
38
+ if cbor.is_a?(Array)
39
+ # an array of emitted events
40
+ if cbor[0].respond_to?(:response)
41
+ # initial response exists
42
+ # it must be the first event arrived
43
+ resp_struct = cbor.shift.response
44
+ else
45
+ resp_struct = context.operation.output.shape.struct_class.new
46
+ end
47
+
48
+ rules.shape.members.each do |name, ref|
49
+ if ref.eventstream
50
+ resp_struct.send("#{name}=", cbor.to_enum)
51
+ end
52
+ end
53
+ resp_struct
54
+ else
55
+ Parser.new(
56
+ rules,
57
+ query_compatible: query_compatible?(context)
58
+ ).parse(cbor)
59
+ end
60
+ else
61
+ EmptyStructure.new
62
+ end
63
+ end
64
+
65
+ def apply_request_id(context)
66
+ context[:request_id] = context.http_response.headers['x-amzn-requestid']
67
+ end
68
+
69
+ def query_compatible?(context)
70
+ context.config.api.metadata.key?('awsQueryCompatible')
71
+ end
72
+ end
73
+ end
74
+ end