Description
Describe the bug
If an S3 endpoint is stubbed out from the client via stub_responses
with a Proc
, any value of context
that results in the Proc
returning nil
will throw an error:
(dev):12:in `<main>': expected params to be a hash, got class NilClass instead. (ArgumentError)
raise ArgumentError, error_messages(errors) unless errors.empty?
It appears to be related to #3204 as the behavior changes when upgrading aws-sdk-s3
from 0.1.181 -> 0.1.182
. It appears that before this PR, nil
was handled separately from Hash
-- with this change, we can end up passing nil
to ParamsValidator.validate!
which causes the unhandled exception
Regression Issue
- Select this option if this issue appears to be a regression.
Expected Behavior
exists?
function returns false
instead of throwing an unhandled exception
Current Behavior
Throws unhandled exception. Here is a stack trace.
eval error: expected params to be a hash, got class NilClass instead.
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/param_validator.rb:35:in `validate!'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/client_stubs.rb:278:in `data_to_http_resp'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/client_stubs.rb:263:in `http_response_stub'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/client_stubs.rb:251:in `convert_stub'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/client_stubs.rb:248:in `convert_stub'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/client_stubs.rb:229:in `next_stub'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/plugins/stub_responses.rb:82:in `stub_responses'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/plugins/stub_responses.rb:73:in `block in call'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/plugins/stub_responses.rb:138:in `block in span_wrapper'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/telemetry/no_op.rb:29:in `in_span'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/plugins/stub_responses.rb:134:in `span_wrapper'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/plugins/stub_responses.rb:72:in `call'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/seahorse/client/plugins/content_length.rb:24:in `call'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-s3-1.182.0/lib/aws-sdk-s3/plugins/s3_signer.rb:78:in `call'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-s3-1.182.0/lib/aws-sdk-s3/plugins/s3_host_id.rb:17:in `call'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-s3-1.182.0/lib/aws-sdk-s3/plugins/http_200_errors.rb:17:in `call'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/xml/error_handler.rb:10:in `call'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/plugins/sign.rb:53:in `call'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/plugins/transfer_encoding.rb:27:in `call'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/plugins/helpful_socket_errors.rb:12:in `call'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-s3-1.182.0/lib/aws-sdk-s3/plugins/s3_signer.rb:53:in `call'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-s3-1.182.0/lib/aws-sdk-s3/plugins/redirects.rb:20:in `call'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/plugins/user_agent.rb:78:in `call'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/plugins/http_checksum.rb:20:in `call'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/plugins/endpoint_pattern.rb:30:in `call'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/plugins/checksum_algorithm.rb:202:in `block in call'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/plugins/user_agent.rb:69:in `metric'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/plugins/checksum_algorithm.rb:212:in `with_metrics'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/plugins/checksum_algorithm.rb:202:in `call'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/plugins/request_compression.rb:94:in `block in call'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/plugins/request_compression.rb:104:in `with_metric'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/plugins/request_compression.rb:94:in `call'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/rest/content_type_handler.rb:27:in `call'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-s3-1.182.0/lib/aws-sdk-s3/plugins/express_session_auth.rb:43:in `block in call'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-s3-1.182.0/lib/aws-sdk-s3/plugins/express_session_auth.rb:49:in `with_metric'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-s3-1.182.0/lib/aws-sdk-s3/plugins/express_session_auth.rb:43:in `call'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-s3-1.182.0/lib/aws-sdk-s3/plugins/expect_100_continue.rb:23:in `call'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-s3-1.182.0/lib/aws-sdk-s3/plugins/bucket_name_restrictions.rb:21:in `call'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/rest/handler.rb:10:in `call'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/plugins/recursion_detection.rb:18:in `call'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/plugins/stub_responses.rb:66:in `call'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-s3-1.182.0/lib/aws-sdk-s3/plugins/endpoints.rb:52:in `block in call'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/plugins/user_agent.rb:69:in `metric'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-s3-1.182.0/lib/aws-sdk-s3/plugins/endpoints.rb:66:in `with_metrics'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-s3-1.182.0/lib/aws-sdk-s3/plugins/endpoints.rb:52:in `call'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/seahorse/client/plugins/endpoint.rb:46:in `call'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/plugins/param_validator.rb:26:in `call'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-s3-1.182.0/lib/aws-sdk-s3/plugins/sse_cpk.rb:24:in `call'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-s3-1.182.0/lib/aws-sdk-s3/plugins/dualstack.rb:21:in `call'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-s3-1.182.0/lib/aws-sdk-s3/plugins/accelerate.rb:43:in `call'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/plugins/checksum_algorithm.rb:169:in `call'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/plugins/jsonvalue_converter.rb:16:in `call'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/plugins/invocation_id.rb:16:in `call'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/plugins/idempotency_token.rb:19:in `call'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/plugins/param_converter.rb:26:in `call'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/plugins/response_paging.rb:12:in `call'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/seahorse/client/plugins/response_target.rb:24:in `call'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/plugins/telemetry.rb:39:in `block in call'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/telemetry/no_op.rb:29:in `in_span'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/plugins/telemetry.rb:53:in `span_wrapper'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/plugins/telemetry.rb:39:in `call'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/seahorse/client/request.rb:72:in `send_request'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/waiters/poller.rb:66:in `block in send_request'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/plugins/user_agent.rb:69:in `metric'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/waiters/poller.rb:65:in `send_request'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/waiters/poller.rb:51:in `call'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/waiters/waiter.rb:107:in `block in poll'
<internal:kernel>:187:in `loop'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/waiters/waiter.rb:104:in `poll'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/waiters/waiter.rb:94:in `block (2 levels) in wait'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/waiters/waiter.rb:93:in `catch'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/waiters/waiter.rb:93:in `block in wait'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/waiters/waiter.rb:92:in `catch'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/waiters/waiter.rb:92:in `wait'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-s3-1.182.0/lib/aws-sdk-s3/waiters.rb:200:in `wait'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-s3-1.182.0/lib/aws-sdk-s3/object.rb:576:in `block in wait_until_exists'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-core-3.220.1/lib/aws-sdk-core/plugins/user_agent.rb:69:in `metric'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-s3-1.182.0/lib/aws-sdk-s3/object.rb:575:in `wait_until_exists'
/Users/devuser/.asdf/installs/ruby/3.3.5/lib/ruby/gems/3.3.0/gems/aws-sdk-s3-1.182.0/lib/aws-sdk-s3/object.rb:556:in `exists?'
Reproduction Steps
c = Aws::S3::Client.new(stub_responses: true)
r = Aws::S3::Resource.new(client: c)
r.client.stub_responses(:head_object, ->(context) {
return {} if context.params[:bucket] == 'abc'
})
r.bucket('abc').object('123').exists? # returns true
r.bucket('abcd').object('123').exists? # throws exception in aws-sdk-s3 0.1.182 but not 0.1.181
This also repros if you replace the stub with the following (though admittedly this example may be less motivating)
r.client.stub_responses(:head_object, nil)
Possible Solution
It appears to be related to #3204 as the behavior changes when upgrading aws-sdk-s3
from 0.1.181 -> 0.1.182
. It appears that before this PR, nil
was handled separately from Hash
-- with this change, we can end up passing nil
to ParamsValidator.validate!
which causes the unhandled exception.
Perhaps something like this? Or adding an explicit branch to handle stub
being nil
diff --git a/gems/aws-sdk-core/lib/aws-sdk-core/client_stubs.rb b/gems/aws-sdk-core/lib/aws-sdk-core/client_stubs.rb
index d4c93d706d..19da14144e 100644
--- a/gems/aws-sdk-core/lib/aws-sdk-core/client_stubs.rb
+++ b/gems/aws-sdk-core/lib/aws-sdk-core/client_stubs.rb
@@ -248,7 +248,7 @@ module Aws
when Proc then convert_stub(operation_name, stub.call(context), context)
when Exception, Class then { error: stub }
when String then service_error_stub(stub)
- else http_response_stub(operation_name, stub)
+ else http_response_stub(operation_name, stub || {})
end
end
Additional Information/Context
While I think I'd prefer a solution that handles nil
, I think an exception that points to the issue a bit more directly would be very helpful! It took us a while to sort this out as the underlying issue on a version upgrade.
Gem name ('aws-sdk', 'aws-sdk-resources' or service gems like 'aws-sdk-s3') and its version
aws-sdk-s3 0.1.182
Environment details (Version of Ruby, OS environment)
Ruby 3.3.5, maxOS Sequoia 15.2 (M2 CPU)