Grape: Router#cascade? undefined method `[]' for nil:NilClass

Created on 10 Jan 2020  路  9Comments  路  Source: ruby-grape/grape

Grape is raising an NoMethodError when implementing a really basic API endpoint.

What I expect

Basic requests to not raise errors.

What actually happens

It raises NoMethodError. Sample output from Minitest (described below):

  1) Error:
GrapeTest#test_api_success:
NoMethodError: undefined method `[]' for nil:NilClass
    /usr/local/bundle/gems/grape-1.2.5/lib/grape/router.rb:164:in `cascade?'
    /usr/local/bundle/gems/grape-1.2.5/lib/grape/router.rb:95:in `transaction'
    /usr/local/bundle/gems/grape-1.2.5/lib/grape/router.rb:72:in `identity'
    /usr/local/bundle/gems/grape-1.2.5/lib/grape/router.rb:57:in `block in call'
    /usr/local/bundle/gems/grape-1.2.5/lib/grape/router.rb:137:in `with_optimization'
    /usr/local/bundle/gems/grape-1.2.5/lib/grape/router.rb:56:in `call'
    /usr/local/bundle/gems/grape-1.2.5/lib/grape/api/instance.rb:165:in `call'
    /usr/local/bundle/gems/grape-1.2.5/lib/grape/api/instance.rb:69:in `call!'
    /usr/local/bundle/gems/grape-1.2.5/lib/grape/api/instance.rb:64:in `call'
    /usr/local/bundle/gems/grape-1.2.5/lib/grape/api.rb:66:in `call'
    /usr/local/bundle/gems/rack-test-1.1.0/lib/rack/mock_session.rb:29:in `request'
    /usr/local/bundle/gems/rack-test-1.1.0/lib/rack/test.rb:266:in `process_request'
    /usr/local/bundle/gems/rack-test-1.1.0/lib/rack/test.rb:129:in `custom_request'
    /usr/local/bundle/gems/rack-test-1.1.0/lib/rack/test.rb:58:in `get'
    /usr/local/lib/ruby/2.3.0/forwardable.rb:202:in `get'
    /app/test/contrib/grape/request_test.rb:25:in `test_api_success'

Steps to reproduce

Dependencies from Gemfile.lock

grape (1.2.5)
      activesupport
      builder
      mustermann-grape (~> 1.0.0)
      rack (>= 1.3.0)
      rack-accept
      virtus (>= 1.0.0)
rack (2.1.0)
rack-accept (0.4.5)
      rack (>= 0.4)
rack-protection (2.0.8.1)
      rack
rack-test (1.1.0)
      rack (>= 1.0, < 3)
mustermann (1.1.1)
      ruby2_keywords (~> 0.0.1)
mustermann-grape (1.0.1)
      mustermann (>= 1.0.0)
virtus (1.0.5)
      axiom-types (~> 0.1)
      coercible (~> 1.0)
      descendants_tracker (~> 0.0, >= 0.0.3)
      equalizer (~> 0.0, >= 0.0.9)

Minitest:

require 'rack/test'
require 'grape'

class TestingAPI < Grape::API
  namespace :base do
    desc 'Returns a success message'
    get :success do
      'OK'
    end
  end
end

class GrapeTest < MiniTest::Test
  include Rack::Test::Methods

  def app
    TestingAPI
  end

  def test_api_success
    get '/base/success'
    assert last_response.ok?
    assert_equal('OK', last_response.body)
  end
end

Other thoughts

This did happen after updating my Gemfile.lock, which showed the following being updated:

Installing ruby2_keywords 0.0.2
Installing mustermann 1.1.1
Installing mustermann-grape 1.0.1

Between looking at the changes in those repos and the stacktrace, I didn't see anything that would explain it.

Digging deeper into the Grape code, what I found in middleware/base.rb was:

begin
  @app_response = @app.call(@env)
ensure
  begin
    after_response = after
  rescue StandardError => e
    warn "caught error of type #{e.class} in after callback inside #{self.class.name} : #{e.message}"
    raise e
  end
end

response = after_response || @app_response

Running this through my test, @app_response was [200, {}, ["OK"]], and after_response was derived from ["/usr/local/bundle/gems/grape-1.2.5/lib/grape/middleware/formatter.rb", 21] which returned:

#<Rack::Response:0x0000561ee6ee7a40
 @block=nil,
 @body=["OK"],
 @buffered=false,
 @header={"Content-Type"=>"text/plain"},
 @length=0,
 @status=200,
 @writer=#<Method: Rack::Response(Rack::Response::Helpers)#append>>

So it would select after_response which would return #<Rack::Response> to rack/head.rb which parsed it into an Array as [#<Rack::Response>, nil nil].

This Array is then eventually passed to Router#cascade? where it would fail because the second argument is nil:

def cascade?(response)
  response && response[1][Grape::Http::Headers::X_CASCADE] == 'pass'
end

Most helpful comment

@delner Grape 1.3.0 was released, feel free to upgrade :rocket: :smile:

All 9 comments

For reference, this is the commit that removes the ability of Rack::Respond to be behave like an array (released Jan 10th 2020 in Rack v2.1.0): https://github.com/rack/rack/commit/72959ebc2f300f3b2ccb7ae2aae9f199e611dfb6

Thanks, does anyone care to try and fix this in grape to be back and forward compatible?

https://github.com/ruby-grape/grape/blob/v1.2.5/lib/grape/router.rb#L163

I'll see what I can do.

https://github.com/ruby-grape/grape/pull/1956 already fixed this and tests in on multiple versions of rack which all pass.

Wasn't aware of this fix; thanks @ioquatix!

Great, thanks @ioquatix.

We have #1958. Maybe someone can jump on it soon.

@delner you might downgrade Rack to 2.0.8, later upgrade Grape to 1.3.0 :slightly_smiling_face:

@delner Grape 1.3.0 was released, feel free to upgrade :rocket: :smile:

Thanks @dnesteryuk !

Was this page helpful?
0 / 5 - 0 ratings