Skip to content

'undefined method key?' when calling declared when params contain an empty array. #2551

Open
@astlouisf

Description

@astlouisf

The test added by this commit fails with

Grape::API::Helpers defines parameters
     Failure/Error: return unless options[:include_missing] || passed_params.key?(declared_param)

     NoMethodError:
       undefined method 'key?' for an instance of String
     # ./lib/grape/dsl/inside_route.rb:82:in 'Grape::DSL::InsideRoute::PostBeforeFilter#declared_hash_attr'
     # ./lib/grape/dsl/inside_route.rb:57:in 'block in Grape::DSL::InsideRoute::PostBeforeFilter#declared_hash'
     # ./lib/grape/dsl/inside_route.rb:54:in 'Array#each'
     # ./lib/grape/dsl/inside_route.rb:54:in 'Enumerable#each_with_object'
     # ./lib/grape/dsl/inside_route.rb:54:in 'Grape::DSL::InsideRoute::PostBeforeFilter#declared_hash'
     # ./lib/grape/dsl/inside_route.rb:35:in 'Grape::DSL::InsideRoute::PostBeforeFilter#declared'
     # ./lib/grape/dsl/inside_route.rb:49:in 'block in Grape::DSL::InsideRoute::PostBeforeFilter#declared_array'
     # ./lib/grape/dsl/inside_route.rb:48:in 'Array#map'
     # ./lib/grape/dsl/inside_route.rb:48:in 'Grape::DSL::InsideRoute::PostBeforeFilter#declared_array'
     # ./lib/grape/dsl/inside_route.rb:33:in 'Grape::DSL::InsideRoute::PostBeforeFilter#declared'
     # ./lib/grape/dsl/inside_route.rb:76:in 'block (2 levels) in Grape::DSL::InsideRoute::PostBeforeFilter#declared_hash_attr'
     # ./lib/grape/dsl/inside_route.rb:99:in 'Grape::DSL::InsideRoute::PostBeforeFilter#handle_passed_param'
     # ./lib/grape/dsl/inside_route.rb:75:in 'block in Grape::DSL::InsideRoute::PostBeforeFilter#declared_hash_attr'
     # ./lib/grape/dsl/inside_route.rb:64:in 'Hash#each_pair'
     # ./lib/grape/dsl/inside_route.rb:64:in 'Grape::DSL::InsideRoute::PostBeforeFilter#declared_hash_attr'
     # ./lib/grape/dsl/inside_route.rb:57:in 'block in Grape::DSL::InsideRoute::PostBeforeFilter#declared_hash'
     # ./lib/grape/dsl/inside_route.rb:54:in 'Array#each'
     # ./lib/grape/dsl/inside_route.rb:54:in 'Enumerable#each_with_object'
     # ./lib/grape/dsl/inside_route.rb:54:in 'Grape::DSL::InsideRoute::PostBeforeFilter#declared_hash'
     # ./lib/grape/dsl/inside_route.rb:35:in 'Grape::DSL::InsideRoute::PostBeforeFilter#declared'
     # ./spec/grape/api/array_issue_spec.rb:13:in 'block (4 levels) in <top (required)>'
     # ./lib/grape/endpoint.rb:62:in 'UnboundMethod#bind_call'
     # ./lib/grape/endpoint.rb:62:in 'block (2 levels) in Grape::Endpoint.generate_api_method'
     # /usr/local/bundle/gems/ruby/3.4.0/gems/activesupport-8.0.2/lib/active_support/notifications.rb:212:in 'ActiveSupport::Notifications.instrument'
     # ./lib/grape/endpoint.rb:61:in 'block in Grape::Endpoint.generate_api_method'
     # ./lib/grape/endpoint.rb:287:in 'Grape::Endpoint#execute'
     # ./lib/grape/endpoint.rb:267:in 'block in Grape::Endpoint#run'
     # /usr/local/bundle/gems/ruby/3.4.0/gems/activesupport-8.0.2/lib/active_support/notifications.rb:212:in 'ActiveSupport::Notifications.instrument'
     # ./lib/grape/endpoint.rb:250:in 'Grape::Endpoint#run'
     # ./lib/grape/endpoint.rb:394:in 'block in Grape::Endpoint#build_stack'
     # ./lib/grape/middleware/base.rb:33:in 'Grape::Middleware::Base#call!'
     # ./lib/grape/middleware/base.rb:26:in 'Grape::Middleware::Base#call'
     # ./lib/grape/middleware/error.rb:34:in 'block in Grape::Middleware::Error#call!'
     # ./lib/grape/middleware/error.rb:34:in 'Kernel#catch'
     # ./lib/grape/middleware/error.rb:34:in 'Grape::Middleware::Error#call!'
     # ./lib/grape/middleware/base.rb:26:in 'Grape::Middleware::Base#call'
     # /usr/local/bundle/gems/ruby/3.4.0/gems/rack-3.1.12/lib/rack/head.rb:15:in 'Rack::Head#call'
     # ./lib/grape/endpoint.rb:225:in 'Grape::Endpoint#call!'
     # ./lib/grape/endpoint.rb:219:in 'Grape::Endpoint#call'
     # ./lib/grape/router/route.rb:27:in 'Grape::Router::Route#exec'
     # ./lib/grape/router.rb:139:in 'Grape::Router#process_route'
     # ./lib/grape/router.rb:86:in 'block in Grape::Router#identity'
     # ./lib/grape/router.rb:119:in 'Grape::Router#transaction'
     # ./lib/grape/router.rb:84:in 'Grape::Router#identity'
     # ./lib/grape/router.rb:68:in 'block in Grape::Router#call'
     # ./lib/grape/router.rb:155:in 'Grape::Router#with_optimization'
     # ./lib/grape/router.rb:67:in 'Grape::Router#call'
     # ./lib/grape/api/instance.rb:164:in 'Grape::API::Instance#call'
     # ./lib/grape/api/instance.rb:68:in 'Grape::API::Instance.call!'
     # ./lib/grape/api/instance.rb:63:in 'Grape::API::Instance.call'
     # /usr/local/bundle/gems/ruby/3.4.0/gems/rack-test-2.2.0/lib/rack/test.rb:360:in 'Rack::Test::Session#process_request'
     # /usr/local/bundle/gems/ruby/3.4.0/gems/rack-test-2.2.0/lib/rack/test.rb:163:in 'Rack::Test::Session#custom_request'
     # /usr/local/bundle/gems/ruby/3.4.0/gems/rack-test-2.2.0/lib/rack/test.rb:112:in 'Rack::Test::Session#post'
     # ./spec/grape/api/array_issue_spec.rb:28:in 'block (2 levels) in <top (required)>'

Somehow1, params contains {"z" => [""]}.

I'm new to ruby and I'm not sure how Grape actually works. My current assumptions are that Grape validates params regardless of if declared is called or not; declared is only used to extract declared params according to the params schema. With that it mind, it feels like validation let through an array that shouldn't have passed validation.

Applying

diff --git a/spec/grape/api/array_issue_spec.rb b/spec/grape/api/array_issue_spec.rb
index d8ff4af1..5f1a5bbf 100644
--- a/spec/grape/api/array_issue_spec.rb
+++ b/spec/grape/api/array_issue_spec.rb
@@ -4,7 +4,7 @@ describe Grape::API::Helpers do
   subject do
     Class.new(Grape::API) do
       params do
-        optional :z, type: Array do
+        requires :z, type: Array do
           requires :a, type: Integer
         end
       end

result in a 400 status code. This points to an issue specific to the use of both optional and type: Array.

Footnotes

  1. I suspect it has to do with how form encoded bodies can't effectively encode an empty array that's not absent.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions