Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Should error! and error_response have the same method signature? #889

Closed
sunnyrjuneja opened this issue Jan 12, 2015 · 12 comments
Closed

Should error! and error_response have the same method signature? #889

sunnyrjuneja opened this issue Jan 12, 2015 · 12 comments

Comments

@sunnyrjuneja
Copy link
Contributor

I was trying to use error! inside of a rescue_from block and got a NoMethodError. I later found error_response and noticed that you don't get the same level of customization that you do from error!.

I'm proposing that error_response and error! have the same method signature. Their current implementations are as follows:

def error_response(error = {})
  status = error[:status] || options[:default_status]
  message = error[:message] || options[:default_message]
  headers = { 'Content-Type' => content_type }
  headers.merge!(error[:headers]) if error[:headers].is_a?(Hash)
  backtrace = error[:backtrace] || []
  rack_response(format_message(message, backtrace), status, headers)
end

def error!(message, status = nil, headers = nil)
  self.status(status || namespace_inheritable(:default_error_status))
  throw :error, message: message, status: self.status, headers: headers
end

My use case was to use error_response like I'm using error! below:

error!({id: 'not_found', message: 'Not Found.'}, 404)

Instead, I'm skipping using error_response and returning a Rack::Response object like this:

Rack::Response.new({
  id: 'interal_error',
  message: 'Interal server error. Check logs.' 
}.to_json, 500, { 'Content-type' => 'text/error' }).finish

Apologies if I'm misunderstanding the API and there's a simple way to do the same thing.

@dblock
Copy link
Member

dblock commented Jan 13, 2015

There's a lot of legacy around this, so most of the reasons for it to be this way are historical and not practical. Try making a PR that changes things the way you describe, I think it's definitely a worthy endeavor.

@sunnyrjuneja
Copy link
Contributor Author

@dblock I just got a chance to work on this and I was a bit confused. One big difference between the two method signatures is that error_response accepts a backtrace and error! doesn't.

When I run this example, I don't see a backtrace from /error. I do see one for /standard_error. It isn't clear to me if rack or grape is providing the backtrace for /standard_error.

module Example
  class API < Grape::API
    format :json

    get :standard_error do
      raise StandardError.new("hello")
    end

    get :error do
      raise Grape::Exceptions::Base
    end
  end
end

From what I can tell, the formatter shouldn't be swallowing the backtrace:
https://github.com/intridea/grape/blob/master/lib/grape/error_formatter/json.rb

Is rack providing the backtrace or grape?

Is is it the default behavior for formatters to swallow backtraces? In that case, it makes sense for the backtrace to be dropped as an argument.

@dblock
Copy link
Member

dblock commented Jan 17, 2015

I think the backtrace of where the exception was raised is (should be) useful in both scenarios and the error formatter or whoever returns something should have access to it and be able to render it.

@adamgotterer
Copy link
Contributor

I don't believe error! can be thrown in an exception context (like from a rescue) so I'd imagine you don't actually have an exception backtrace even available.

What I think you're looking for is https://github.com/intridea/grape/blob/master/lib/grape/middleware/error.rb line 70. You'll see the backtrace comes from the exception and then is passed to format_message.

sunnyrjuneja pushed a commit to sunnyrjuneja/grape that referenced this issue Jan 23, 2015
sunnyrjuneja pushed a commit to sunnyrjuneja/grape that referenced this issue Jan 23, 2015
sunnyrjuneja pushed a commit to sunnyrjuneja/grape that referenced this issue Jan 23, 2015
@sunnyrjuneja
Copy link
Contributor Author

I just gave my first go at here.

I tried my best to change as little as code as possible while matching the method signature of error! but I'm not sure how I should resolve the test cases that are currently failing.

Most of the failures stem from the fact that handle_error returns a hash whose structure I'm respecting:

      def handle_error(e)
        error_response(message: e.message, backtrace: e.backtrace)
      end

The tests expect that I return whatever's in the key :message but the point of the PR was to respect whatever was passed to error_response.

Here's a sample of what I'm talking about:

  1) Grape::API custom middleware .use mounts behind error middleware
     Failure/Error: expect(last_response.body).to eq('Caught in the Net')

       expected: "Caught in the Net"
            got: "{\"message\":\"Caught in the Net\"}"

       (compared using ==)
     # ./spec/grape/api_spec.rb:981:in `block (4 levels) in <top (required)>'

Any thoughts on how to proceed? I know it may not be clear what I'm saying, so feel free to ask me to elaborate.

@dblock
Copy link
Member

dblock commented Jan 25, 2015

It's possible that you need to change the tests. Obviously you want to be able to render both a String ("Caught in the Net") and a JSON (message: ...), so as long as the tests cover both you're good. The important thing is that for those that used to just raise or error! in the API don't have to change a thing and get the same result.

The next thing to think about is to explain in UPGRADING what users who called error_response directly are supposed to do.

@sunnyrjuneja
Copy link
Contributor Author

@dblock I'll give it some thought. I'll work on the tests this week and hopefully have something in mind for UPGRADING.

sunnyrjuneja pushed a commit to sunnyrjuneja/grape that referenced this issue Mar 27, 2015
@sunnyrjuneja
Copy link
Contributor Author

Hey @dblock, I apologize for the delay on this. However, I believe I've found a better solution. I've created a method called error! that wraps error_response.

My recommendation is to encourage the use of error! over rack_response. By doing so, we can do the following:

format :json
subject.rescue_from Grape::Exceptions::ValidationErrors do |e|
  rack_response e.to_json, 400
end

# Becomes
format :json
subject.rescue_from Grape::Exceptions::ValidationErrors do |e|
  error! e, 400
end

People using error_response need not to change. error_response be a quick way to send an error while error! can maintain api compatibility with other error! and allow more fine-grained control.

If someone decides to use format :xml, it should update no problem without needing to change the code. I can update the README and tests if you want. I've included a commit that shows the implementation.

However, this does pose one problem: although I had updated all the tests, there was one that I couldn't get to pass. Would you mind taking a look?

@sunnyrjuneja
Copy link
Contributor Author

Here is the failing test: sunnyrjuneja@844b5c9

Here is the branch: https://github.com/whatasunnyday/grape/tree/error-new

Once again, I'm sorry for the extreme delay on this PR. I hope adding this is still interesting to you.

@dblock
Copy link
Member

dblock commented Mar 31, 2015

It is, would love an attempt at a fix?

@sunnyrjuneja
Copy link
Contributor Author

@dblock I gave it a shot. Any idea why it might be failing? Here's the stacktrace:

  1) Grape::Exceptions::ValidationErrors api can return structured json with separate fields
     Failure/Error: error!(e, 400)
     ArgumentError:
       wrong number of arguments (1 for 0)
     # ./lib/grape/exceptions/validation_errors.rb:27:in `as_json'
     # /home/wasd/.gem/ruby/2.2.0/gems/activesupport-4.2.0/lib/active_support/core_ext/object/json.rb:159:in `block in as_json'
     # /home/wasd/.gem/ruby/2.2.0/gems/activesupport-4.2.0/lib/active_support/core_ext/object/json.rb:159:in `each'
     # /home/wasd/.gem/ruby/2.2.0/gems/activesupport-4.2.0/lib/active_support/core_ext/object/json.rb:159:in `map'
     # /home/wasd/.gem/ruby/2.2.0/gems/activesupport-4.2.0/lib/active_support/core_ext/object/json.rb:159:in `as_json'
     # /home/wasd/.gem/ruby/2.2.0/gems/activesupport-4.2.0/lib/active_support/json/encoding.rb:34:in `encode'
     # /home/wasd/.gem/ruby/2.2.0/gems/activesupport-4.2.0/lib/active_support/json/encoding.rb:21:in `encode'
     # /home/wasd/.gem/ruby/2.2.0/gems/activesupport-4.2.0/lib/active_support/core_ext/object/json.rb:37:in `to_json_with_active_support_encoder'
     # /home/wasd/.gem/ruby/2.2.0/gems/multi_json-1.10.1/lib/multi_json/adapters/json_common.rb:21:in `dump'
     # /home/wasd/.gem/ruby/2.2.0/gems/multi_json-1.10.1/lib/multi_json/adapter.rb:24:in `dump'
     # /home/wasd/.gem/ruby/2.2.0/gems/multi_json-1.10.1/lib/multi_json.rb:136:in `dump'
     # ./lib/grape/error_formatter/json.rb:12:in `call'
     # ./lib/grape/middleware/error.rb:86:in `format_message'
     # ./lib/grape/middleware/error.rb:75:in `error_response'
     # ./lib/grape/middleware/error.rb:62:in `error!'
     # ./spec/grape/exceptions/validation_errors_spec.rb:30:in `block (4 levels) in <top (required)>'
     # ./lib/grape/middleware/error.rb:57:in `instance_exec'
     # ./lib/grape/middleware/error.rb:57:in `exec_handler'
     # ./lib/grape/middleware/error.rb:38:in `rescue in call!'
     # ./lib/grape/middleware/error.rb:25:in `call!'
     # ./lib/grape/middleware/base.rb:18:in `call'
     # /home/wasd/.gem/ruby/2.2.0/gems/rack-1.6.0/lib/rack/head.rb:13:in `call'
     # /home/wasd/.gem/ruby/2.2.0/gems/rack-1.6.0/lib/rack/builder.rb:153:in `call'
     # ./lib/grape/endpoint.rb:196:in `call!'
     # ./lib/grape/endpoint.rb:184:in `call'
     # /home/wasd/.gem/ruby/2.2.0/gems/rack-mount-0.8.3/lib/rack/mount/route_set.rb:152:in `block in call'
     # /home/wasd/.gem/ruby/2.2.0/gems/rack-mount-0.8.3/lib/rack/mount/code_generation.rb:96:in `block in recognize'
     # /home/wasd/.gem/ruby/2.2.0/gems/rack-mount-0.8.3/lib/rack/mount/code_generation.rb:68:in `optimized_each'
     # /home/wasd/.gem/ruby/2.2.0/gems/rack-mount-0.8.3/lib/rack/mount/code_generation.rb:95:in `recognize'
     # /home/wasd/.gem/ruby/2.2.0/gems/rack-mount-0.8.3/lib/rack/mount/route_set.rb:141:in `call'
     # ./lib/grape/api.rb:98:in `call'
     # ./lib/grape/api.rb:33:in `call!'
     # ./lib/grape/api.rb:29:in `call'
     # /home/wasd/.gem/ruby/2.2.0/gems/rack-test-0.6.3/lib/rack/mock_session.rb:30:in `request'
     # /home/wasd/.gem/ruby/2.2.0/gems/rack-test-0.6.3/lib/rack/test.rb:244:in `process_request'
     # /home/wasd/.gem/ruby/2.2.0/gems/rack-test-0.6.3/lib/rack/test.rb:58:in `get'
     # ./spec/grape/exceptions/validation_errors_spec.rb:41:in `block (3 levels) in <top (required)>'

@dblock
Copy link
Member

dblock commented Apr 1, 2015

This json signature is incorrect. The method should be taking an argument as all these as_json methods do by "design", so def as_json(_ = {}). Please fix it in Grape.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants