Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Grape: Rescue from invalid JSON

First:

I'm using grape for building my API (Rails 4). When someone is sending an invalid JSON body (e.g. forgot the last }), the following error is raised:

ActionDispatch::ParamsParser::ParseError (795: unexpected token at '{"foobar": 1234

')

I tried it with grapes rescue_from :all option, but this doesn't work. Inside the stacktrace, I don't see the grape gem involved. It seems that this error is thrown from actionpack:

  .gems/gems/actionpack-4.1.4/lib/action_dispatch/middleware/params_parser.rb:53:in `rescue in parse_formatted_parameters'
  .gems/gems/actionpack-4.1.4/lib/action_dispatch/middleware/params_parser.rb:32:in `parse_formatted_parameters'
  .gems/gems/actionpack-4.1.4/lib/action_dispatch/middleware/params_parser.rb:23:in `call'

But what would be the best way to catch those errors, return a 400: Bad Request errors, and include the unexpected token at '{"foobar": 1234 message inside the json response?

Second:

I tried to test this with RSpec, but didn't have any luck on sending a raw request with an invalid JSON. I tried it with

post "/my_route", '{some invalid json'

but this doesn't throw the error from above. I thought since Rails 4, the second parameter, passed as a string, is treated like the raw body?

like image 291
23tux Avatar asked Mar 02 '15 14:03

23tux


1 Answers

Unfortunately ActionDispatch runs well ahead of it ever getting to a controller so you're not going to be able to do this with Grape (AFAIK).

We ran into this issue too and found a wonderful article from the Thoughtbot guys on the subject.

Use the Curb gem to make the shotty calls:

require 'curb'
it 'sends poorly formatted json' do
  broken_json = %Q|{"notice":{"title":"A sweet title"#{invalid_tokens}}}|
  resp = Curl.post("http://#{host}:#{port}#{path}", broken_json)

  expect(resp.response_code).to eq 500
end

Thoughtbot recommends writing a middleware class to capture future JSON parse errors like this:

# in app/middleware/catch_json_parse_errors.rb
class CatchJsonParseErrors
  def initialize(app)
    @app = app
  end

  def call(env)
    begin
      @app.call(env)
    rescue ActionDispatch::ParamsParser::ParseError => error
      if env['HTTP_ACCEPT'] =~ /application\/json/
        error_output = "There was a problem in the JSON you submitted: #{error}"
        return [
          400, { "Content-Type" => "application/json" },
          [ { status: 400, error: error_output }.to_json ]
        ]
      else
        raise error
      end
    end
  end
end
like image 200
Anthony Avatar answered Oct 16 '22 01:10

Anthony