Skip to content

Stubbing nil response in Aws::S3::Client.stub_responses throws unhandled exception #3210

Closed
@cjubb39

Description

@cjubb39

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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    wontfixWe have determined that we will not resolve the issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions