7

I'm on Rails 2.3.5. In a typical user controller create action

class UsersController
  def create
    @user = User.new(params[:user])
    respond_to do |format]
      if @user.save ...
      else
        format.json ....
      end        
  end
end

When a client passes an invalid / malformed JSON string for input, Rails throw a 500 internal server error saying "Invalid JSON string". Is it possible for me to trap the error so I can give a custom message?

UPDATED Here's the stacktrace as requested. Just so I'm clear, I know this is a malformed JSON string, my question is not how to fix the JSON string but rather how to trap this particular error so I can send back a more meaningful error message than HTTP 500 Internal Server Error. Thanks in advance for your help

Error occurred while parsing request parameters.
Contents:

user: {login: "John", email: "[email protected]", password: "111"}}
/!\ FAILSAFE /!\  Mon Nov 08 02:01:04 -0800 2010

  Status: 500 Internal Server Error
  Invalid JSON string
    c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.3.5/lib/active_support/json/backends/yaml.rb:14:in `decode'
    c:1:in `__send__'
    c:1:in `decode'
    c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.3.5/lib/action_controller/params_parser.rb:42:in `parse_formatted_parameters'
    c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.3.5/lib/action_controller/params_parser.rb:11:in `call'
    c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.3.5/lib/action_controller/session/cookie_store.rb:93:in `call'
    c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.3.5/lib/action_controller/failsafe.rb:26:in `call'
    c:/ruby/lib/ruby/gems/1.8/gems/rack-1.0.1/lib/rack/lock.rb:11:in `call'
    c:/ruby/lib/ruby/gems/1.8/gems/rack-1.0.1/lib/rack/lock.rb:11:in `synchronize'
    c:/ruby/lib/ruby/gems/1.8/gems/rack-1.0.1/lib/rack/lock.rb:11:in `call'
    c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.3.5/lib/action_controller/dispatcher.rb:114:in `call'
    c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.3.5/lib/action_controller/reloader.rb:34:in `run'
    c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.3.5/lib/action_controller/dispatcher.rb:108:in `call'
    c:/ruby/lib/ruby/gems/1.8/gems/rails-2.3.5/lib/rails/rack/static.rb:31:in `call'
    c:/ruby/lib/ruby/gems/1.8/gems/rack-1.0.1/lib/rack/urlmap.rb:46:in `call'
    c:/ruby/lib/ruby/gems/1.8/gems/rack-1.0.1/lib/rack/urlmap.rb:40:in `each'
    c:/ruby/lib/ruby/gems/1.8/gems/rack-1.0.1/lib/rack/urlmap.rb:40:in `call'
    c:/ruby/lib/ruby/gems/1.8/gems/rails-2.3.5/lib/rails/rack/log_tailer.rb:17:in `call'
    ...
    c:/ruby/lib/ruby/gems/1.8/gems/rack-1.0.1/lib/rack/handler/mongrel.rb:34:in `run'
    c:/ruby/lib/ruby/gems/1.8/gems/rails-2.3.5/lib/commands/server.rb:111
    c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in `gem_original_require'
    c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in `require'
    script/server:3
re.rb:31:in `require'
    script/server:3
1
  • can you put a complete backtrace, please ? Commented Nov 8, 2010 at 10:36

3 Answers 3

6

I've found this rails plugin to work on rails 3.0.5: https://github.com/kares/request_exception_handler

As Rob Cameron suggests it loads your rails application and raises the parseError from your application code. I haven't tested it's performance impact, but it does seem to work well.

Sign up to request clarification or add additional context in comments.

Comments

0

Looking in /usr/lib/ruby/gems/1.8/gems/activesupport-2.3.8/lib/active_support/json/backends/yaml.rb (admittedly a slightly different version), it looks like a ParseError is being raised. Assuming the error is occurring at "format.json", you could try something like

begin
  format.json
rescue ParseError => e
  render :text => "Now there's some ugly JSON! (#{e.message})", :status => 500
end

If that doesn't work, you can try to catch it earlier using the rescue_from callback in your controller.

class UsersController < ApplicationController
...
  rescue_from ParseError do |e|
    render ...
  end
...
end

3 Comments

Thanks for the suggestion, unfortunately the error isn't raised at format.json. I believe it's raised before the user/create action is called because Rails needs to parse the JSON string to get access to params.
Ah, I guess that makes sense. In that case, try the rescue_from callback. I've added an example up above.
Another good idea but alas, no dice, the error is raised before it even hits the controller
0

I'm running into the same issue right now -- I have an API that someone could pass invalid JSON to and I want to be smart about the error I return. I'm running Rails 3.0.5. I traced the error being thrown to lib/active_support/json/backends/yaml.rb line 17:

def decode(json)
  if json.respond_to?(:read)
    json = json.read
  end
  YAML.load(convert_json_to_yaml(json))
rescue ArgumentError
  raise ParseError, "Invalid JSON string"
end

So, json.read fails and throws the error. Unfortunately this happens long before your controller is ever invoked (it's part of the initial request params parsing where Rails ties into Rack). I would think that in order to catch this you would need to add a little monkey-patching to overwrite some of the built-in error throwing and instead bubble up something you can use...maybe you could add a header to the request object that you could then detect in your controller and throw your own error.

Unfortunately I don't think this will work for me since I only want to override the behavior when accessing the API, not the rest of the site. Although I suppose in the monkey-patch, assuming I have access to the request object, I could tell it to only use my new behavior if the requested path matches a regex like /\/api\//

1 Comment

I have solved this problem using Rack middleware. The middleware is inserted before ActionDispatch::ParamsParser and catches the errors emitted by @app.call. Since controller actions are also rack apps in rails applications, I can use a controller action to render a pretty error message using all of rails templating. (Posting for other folk who get here from Google) gist.github.com/4709748

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.