Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
1 task done
cjubb39 opened this issue Mar 12, 2025 · 3 comments
Open
1 task done
Labels
wontfix We have determined that we will not resolve the issue.

Comments

@cjubb39
Copy link

cjubb39 commented Mar 12, 2025

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)

@cjubb39 cjubb39 added bug This issue is a bug. needs-triage This issue or PR still needs to be triaged. labels Mar 12, 2025
@github-actions github-actions bot added the potential-regression Marking this issue as a potential regression to be checked by team member label Mar 12, 2025
@mullermp
Copy link
Contributor

Thanks for opening an issue. Head object (or any response) should never return nil. Relying on this in your test is relying on SDK behavior that will never occur. Why are you trying to stub nil?

@mullermp mullermp added response-requested Waiting on additional info and feedback. Will move to "closing-soon" in 7 days. wontfix We have determined that we will not resolve the issue. and removed bug This issue is a bug. needs-triage This issue or PR still needs to be triaged. potential-regression Marking this issue as a potential regression to be checked by team member labels Mar 12, 2025
@cjubb39
Copy link
Author

cjubb39 commented Mar 12, 2025

I thought that might be the response. It was just an oversight -- nothing intentional. We've ultimately just updated the "Not Found" branch on object_head from nil -> {body: nil, headers: [], status_code: 404}

If that's something you'd rather not fix, maybe there could be a more clear exception thrown -- or I'm not sure if it's too late to update the changelog to note this? I ask because I imagine it might be common problem as it seemed to even be something you ran into while writing up that PR that disallowed it

@mullermp
Copy link
Contributor

The issue with the proc is that you can't do validation until it's request time anyway (because it takes a context). If you're looking to do an empty stub, you can simply just stub {} always or in special cases stub the data you need. I don't know if you specifically need an http stub (body, headers, status_code) for that. You can also stub the error directly with s3.stub_responses(:head_object, 'NotFound')

@github-actions github-actions bot removed the response-requested Waiting on additional info and feedback. Will move to "closing-soon" in 7 days. label Mar 13, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
wontfix We have determined that we will not resolve the issue.
Projects
None yet
Development

No branches or pull requests

2 participants