Skip to content

Commit

Permalink
Support awsQueryCompatible in CBOR
Browse files Browse the repository at this point in the history
  • Loading branch information
mullermp committed Oct 14, 2024
1 parent 9becb4d commit dcec497
Show file tree
Hide file tree
Showing 9 changed files with 270 additions and 153 deletions.
14 changes: 7 additions & 7 deletions gems/aws-sdk-core/lib/aws-sdk-core/client_stubs.rb
Original file line number Diff line number Diff line change
Expand Up @@ -307,14 +307,14 @@ def data_to_http_resp(operation_name, data)

def protocol_helper
case config.api.metadata['protocol']
when 'json' then Stubbing::Protocols::Json
when 'rest-json' then Stubbing::Protocols::RestJson
when 'rest-xml' then Stubbing::Protocols::RestXml
when 'query' then Stubbing::Protocols::Query
when 'ec2' then Stubbing::Protocols::EC2
when 'json' then Stubbing::Protocols::Json
when 'rest-json' then Stubbing::Protocols::RestJson
when 'rest-xml' then Stubbing::Protocols::RestXml
when 'query' then Stubbing::Protocols::Query
when 'ec2' then Stubbing::Protocols::EC2
when 'smithy-rpc-v2-cbor' then Stubbing::Protocols::RpcV2
when 'api-gateway' then Stubbing::Protocols::ApiGateway
else raise "unsupported protocol"
when 'api-gateway' then Stubbing::Protocols::ApiGateway
else raise 'unsupported protocol'
end.new
end
end
Expand Down
3 changes: 2 additions & 1 deletion gems/aws-sdk-core/lib/aws-sdk-core/json/error_handler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ def extract_error(body, context)
def error_code(json, context)
code =
if aws_query_error?(context)
error = context.http_response.headers['x-amzn-query-error'].split(';')[0]
query_header = context.http_response.headers['x-amzn-query-error']
error, _type = query_header.split(';') # type not supported
remove_prefix(error, context)
else
json['__type']
Expand Down
1 change: 1 addition & 0 deletions gems/aws-sdk-core/lib/aws-sdk-core/json/handler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ def build_request(context)
context.http_request.http_method = 'POST'
context.http_request.headers['Content-Type'] = content_type(context)
context.http_request.headers['X-Amz-Target'] = target(context)
context.http_request.headers['X-Amzn-Query-Mode'] = 'true' if query_compatible?(context)
context.http_request.body = build_body(context)
end

Expand Down
3 changes: 2 additions & 1 deletion gems/aws-sdk-core/lib/aws-sdk-core/rpc_v2/error_handler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ def extract_error(body, context)
def error_code(data, context)
code =
if aws_query_error?(context)
error = context.http_response.headers['x-amzn-query-error'].split(';')[0]
query_header = context.http_response.headers['x-amzn-query-error']
error, _type = query_header.split(';') # type not supported
remove_prefix(error, context)
else
data['__type']
Expand Down
3 changes: 2 additions & 1 deletion gems/aws-sdk-core/lib/aws-sdk-core/rpc_v2/handler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ def with_metric(&block)
end

def build_request(context)
context.http_request.headers['smithy-protocol'] = 'rpc-v2-cbor'
context.http_request.headers['Smithy-Protocol'] = 'rpc-v2-cbor'
context.http_request.headers['X-Amzn-Query-Mode'] = 'true' if query_compatible?(context)
context.http_request.http_method = 'POST'
context.http_request.body = build_body(context)
build_url(context)
Expand Down
19 changes: 8 additions & 11 deletions gems/aws-sdk-core/lib/aws-sdk-core/stubbing/protocols/rpc_v2.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ module Stubbing
module Protocols
class RpcV2

def stub_data(api, operation, data)
def stub_data(_api, operation, data)
resp = Seahorse::Client::Http::Response.new
resp.status_code = 200
resp.headers['Content-Type'] = content_type(api)
resp.headers['Smithy-Protocol'] = 'rpc-v2-cbor'
resp.headers['Content-Type'] = 'application/cbor'
resp.headers['x-amzn-RequestId'] = 'stubbed-request-id'
resp.body = build_body(operation, data)
resp
Expand All @@ -17,21 +18,17 @@ def stub_data(api, operation, data)
def stub_error(error_code)
http_resp = Seahorse::Client::Http::Response.new
http_resp.status_code = 400
http_resp.body = <<-JSON.strip
{
"code": #{error_code.inspect},
"message": "stubbed-response-error-message"
}
http_resp.body = <<~JSON.strip
{
"code": #{error_code.inspect},
"message": "stubbed-response-error-message"
}
JSON
http_resp
end

private

def content_type(api)
'application/cbor'
end

def build_body(operation, data)
Aws::RpcV2::Builder.new(operation.output).serialize(data)
end
Expand Down
196 changes: 64 additions & 132 deletions gems/aws-sdk-core/spec/aws/json/error_handler_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,159 +5,91 @@
module Aws
module Json
describe ErrorHandler do
let(:client) { ApiHelper.sample_json::Client.new(stub_responses: true) }

let(:client) do
ApiHelper.sample_json::Client.new(
region: 'us-west-2',
retry_limit: 0,
credentials: Aws::Credentials.new('akid', 'secret')
)
end

let(:error_resp) { <<-JSON.strip }
{
"__type":"ProvisionedThroughputExceededException",
"message":"foo",
"foo": "bar"
}
let(:error_resp) { <<~JSON.strip }
{
"__type":"ProvisionedThroughputExceededException",
"message":"foo",
"foo": "bar"
}
JSON

let(:invalid_error_resp) { <<-JSON.strip }
{
"__type":"ProvisionedThroughputExceededException:",
"message":"foo",
"foo": "bar"
}
let(:invalid_error_resp) { <<~JSON.strip }
{
"__type":"ProvisionedThroughputExceededException:",
"message":"foo",
"foo": "bar"
}
JSON
let(:unmodeled_resp) { <<-JSON.strip }
{
"__type":"UnModeledException",
"message":"foo"
}

let(:unmodeled_error_resp) { <<~JSON.strip }
{
"__type":"UnModeledException",
"message":"foo"
}
JSON

let(:empty_struct_error) { <<-JSON.strip }
{
"__type":"EmptyStructError",
"message":"foo"
}
let(:empty_struct_error_resp) { <<~JSON.strip }
{
"__type":"EmptyStructError",
"message":"foo"
}
JSON

it 'extracts error data' do
stub_request(:post, "https://dynamodb.us-west-2.amazonaws.com/").
to_return(:status => 400, :body => error_resp)
expect {
client.batch_get_item(
request_items: {}
)
}.to raise_error do |e|
expect(e.code).to eq('ProvisionedThroughputExceededException')
expect(e.message).to eq('foo')
expect(e.foo).to eq('bar')
end
end
client.stub_responses(
:batch_get_item,
{ status_code: 400, body: error_resp, headers: {} }
)

it 'ignore invalid characters when matching' do
stub_request(:post, "https://dynamodb.us-west-2.amazonaws.com/").
to_return(:status => 400, :body => invalid_error_resp)
expect {
client.batch_get_item(
request_items: {}
)
}.to raise_error do |e|
expect(e.code).to eq('ProvisionedThroughputExceededException')
expect(e.message).to eq('foo')
expect(e.foo).to eq('bar')
end
expect { client.batch_get_item(request_items: {}) }
.to raise_error do |e|
expect(e.code).to eq('ProvisionedThroughputExceededException')
expect(e.message).to eq('foo')
expect(e.foo).to eq('bar')
end
end

it 'extracts code and message for unmodeled errors' do
stub_request(:post, "https://dynamodb.us-west-2.amazonaws.com/").
to_return(:status => 400, :body => unmodeled_resp)
expect {
client.batch_get_item(
request_items: {}
)
}.to raise_error do |e|
expect(e.code).to eq('UnModeledException')
expect(e.message).to eq('foo')
end
end
it 'ignore invalid characters when matching' do
client.stub_responses(
:batch_get_item,
{ status_code: 400, body: invalid_error_resp, headers: {} }
)

it 'extracts code and message for modeled empty struct errors' do
stub_request(:post, "https://dynamodb.us-west-2.amazonaws.com/").
to_return(:status => 400, :body => empty_struct_error)
expect {
client.batch_get_item(
request_items: {}
)
}.to raise_error do |e|
expect(e.code).to eq('EmptyStructError')
expect(e.message).to eq('foo')
end
expect { client.batch_get_item(request_items: {}) }
.to raise_error do |e|
expect(e.code).to eq('ProvisionedThroughputExceededException')
expect(e.message).to eq('foo')
expect(e.foo).to eq('bar')
end
end

describe 'AwsQueryCompatible' do
AwsQueryCompatibleErrorClient = ApiHelper.sample_service(
api: {
'metadata' => {
'apiVersion' => '2018-11-07',
'awsQueryCompatible' => {},
'protocol' => 'json',
'jsonVersion' => '1.1',
'endpointPrefix' => 'svc',
'signatureVersion' => 'v4',
'errorPrefix' => 'Prefix' # service customization needs to set this
},
'operations' => {
'Foo' => {
'name' => 'Foo',
'http' => { 'method' => 'POST', 'requestUri' => '/' },
'input' => { 'shape' => 'FooInput'}
}
},
'shapes' => {
'FooInput' => {
'type' => 'structure',
'members' => {}
},
'String' => { 'type' => 'string' }
}
}
).const_get(:Client)

let(:client_aws_query_compatible) do
AwsQueryCompatibleErrorClient.new(
region: 'us-west-2',
retry_limit: 0,
credentials: Aws::Credentials.new('akid', 'secret')
)
end
it 'extracts code and message for unmodeled errors' do
client.stub_responses(
:batch_get_item,
{ status_code: 400, body: unmodeled_error_resp, headers: {} }
)

it 'extracts code and message from x-amzn-query-error' do
stub_request(:post, "https://svc.us-west-2.amazonaws.com/").
to_return(:status => 400, headers: {
'x-amzn-query-error': 'Prefix.AwsQueryError;Sender'
}, :body => error_resp)
expect {
client_aws_query_compatible.foo()
}.to raise_error do |e|
expect(e.code).to eq('AwsQueryError')
expect { client.batch_get_item(request_items: {}) }
.to raise_error do |e|
expect(e.code).to eq('UnModeledException')
expect(e.message).to eq('foo')
end
end
end

it 'fallback to default if missing x-amzn-query-error' do
stub_request(:post, "https://svc.us-west-2.amazonaws.com/").
to_return(:status => 400, headers: {}, :body => error_resp)
expect {
client_aws_query_compatible.foo()
}.to raise_error do |e|
expect(e.code).to eq('ProvisionedThroughputExceededException')
it 'extracts code and message for modeled empty struct errors' do
client.stub_responses(
:batch_get_item,
{ status_code: 400, body: empty_struct_error_resp, headers: {} }
)

expect { client.batch_get_item(request_items: {}) }
.to raise_error do |e|
expect(e.code).to eq('EmptyStructError')
expect(e.message).to eq('foo')
end
end
end
end
end
Expand Down
Loading

0 comments on commit dcec497

Please sign in to comment.