Grape: Rescue from invalid JSON
Asked Answered
C

1

7

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?

Callean answered 2/3, 2015 at 14:7 Comment(1)
I think you should consider separating this question into two questions...Actinometer
B
6

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
Baram answered 2/3, 2015 at 14:27 Comment(7)
Thank you, I'll give it a try! One more question: I don't have capybara running, I just use rspec type: :request specs. How can I find out the current host (should be localhost) and port?Callean
I would just set a constant in your spec_helper for host (equal to localhost).Baram
Ok, the host is clear, but what about the port? Is there a way to find out, on which port rspec is running rails?Callean
if you're always going to run these on localhost:3000 then no need for multiple variables/constants, just do what's best for your current project.Baram
But the tests itself aren't running on port 3000. Its some random portCallean
probably worth another SO question regarding your specific setup.Baram
For our Rails 3.2/Grape app, I needed to rescue from a different error: MultiJson::ParseError. YMMV.Ceraceous

© 2022 - 2024 — McMap. All rights reserved.