Bubble up to the error_formatter the original exception and the backtrace#1652
Conversation
| raise e | ||
| rescue StandardError => e | ||
| throw :error, status: 400, message: e.message | ||
| throw :error, status: 400, message: e.message, backtrace: e |
There was a problem hiding this comment.
Is this a standard pattern somehow in Ruby? I mean I would expect backtrace to be an actual backtrace and not the original exception. Is anyone doing it like this elsewhere?
Are there other places we throw :error like this that also need fixing?
There was a problem hiding this comment.
Is this a standard pattern somehow in Ruby? I mean I would expect backtrace to be an actual backtrace and not the original exception. Is anyone doing it like this elsewhere?
I am quite new in the Ruby world, so TBH I don't know if this is a standard pattern but I would say no. However the Grape library as a "contract" for the formatter: formatter.call(message, backtrace, options, env).
# lib/grape/middleware/error.rb:90
def error_response(error = {})
status = error[:status] || options[:default_status]
message = error[:message] || options[:default_message]
headers = { Grape::Http::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
# lib/grape/middleware/error.rb:103
def format_message(message, backtrace)
format = env[Grape::Env::API_FORMAT] || options[:format]
formatter = Grape::ErrorFormatter.formatter_for(format, options)
throw :error, status: 406, message: "The requested format '#{format}' is not supported." unless formatter
formatter.call(message, backtrace, options, env)
endThis method is called here:
# lib/grape/middleware/error.rb:29
def call!(env)
@env = env
begin
error_response(catch(:error) do
return @app.call(@env)
end)
rescue StandardError => e
# ...
endAre there other places we throw :error like this that also need fixing?
Probably yes, but I didn't investigate any other use cases. This one affected us.
There was a problem hiding this comment.
While it works, backtrace is meant to be a backtrace. So I think either adding an original exception into the contract or passing the actual backtrace is the right thing to do.
There was a problem hiding this comment.
I can do both, what would be your advice on how to test it?
|
Danger is right, this also needs tests. |
|
@dblock would you care to review the code again? I already did the changes you requested (added both backtrace and original exception) and added a test. I will update the changelog as soon as I have some feedback regarding the implementation. |
|
Thanks for hanging in here @dcsg. I am not loving this at all :( I think we're going the wrong way. The following passes all kinds of things here which are all properties of I think what we want to write is this: Does this look feasible? It might mean breaking some backwards compat which I am OK with, but maybe we don't have to? |
|
@dblock did the changes, what do you think about them? |
| headers.merge!(error[:headers]) if error[:headers].is_a?(Hash) | ||
| backtrace = error[:backtrace] || [] | ||
| rack_response(format_message(message, backtrace), status, headers) | ||
| backtrace = error[:backtrace] || error[:original_exception] && error[:original_exception].backtrace || [] |
There was a problem hiding this comment.
@dblock here I could replace this with the following:
backtrace = error[:backtrace] || error[:original_exception]&.backtrace || []
However, rubocop complains because of Ruby 2.1.*. Does Grape still support that Ruby version?
There was a problem hiding this comment.
Grape supports whatever is in https://github.com/ruby-grape/grape/blob/master/.travis.yml, so 2.2+, so we can tame Rubocop accordingly. Since we don't use &. anywhere else maybe just expanding it is better.
dblock
left a comment
There was a problem hiding this comment.
I have some minor comments left and I am OK merging this because it does what it does. However I'd like us to go further and get rid of passing backtrace through, maybe in a future PR?
Basically I want
throw :error, status: 400, message: e.message, backtrace: e.backtrace, exception: eto become
throw :error, status: 400, original_exception: e| def call(message, backtrace, options = {}, env = nil, original_exception = nil) | ||
| result = wrap_message(present(message, env)) | ||
|
|
||
| if (options[:rescue_options] || {})[:backtrace] && backtrace && !backtrace.empty? |
There was a problem hiding this comment.
The if (options[:rescue_options] || {}) is dup, so unwrap it.
rescue_options = options[:rescue_options]
if (rescue_options)
if ...
end| end | ||
|
|
||
| def error!(message, status = options[:default_status], headers = {}, backtrace = []) | ||
| def error!(message, status = options[:default_status], headers = {}, backtrace = [], original_exception = '') |
There was a problem hiding this comment.
Here original_exception should be defaulted to nil.
| headers.merge!(error[:headers]) if error[:headers].is_a?(Hash) | ||
| backtrace = error[:backtrace] || [] | ||
| rack_response(format_message(message, backtrace), status, headers) | ||
| backtrace = error[:backtrace] || error[:original_exception] && error[:original_exception].backtrace || [] |
| end | ||
| rescue Grape::Exceptions::InvalidFormatter => e | ||
| throw :error, status: 500, message: e.message | ||
| throw :error, status: 500, message: e.message, backtrace: e.backtrace, exception: e |
There was a problem hiding this comment.
Sholdn't this be original_exception: e?
| #### Fixes | ||
|
|
||
| * Your contribution here. | ||
| * [#1652](https://github.com/ruby-grape/grape/pull/1652): Bubble up to the error_formatter the root exception - [@dcsg](https://github.com/dcsg). |
Doing this change it will break backwards compatibility. Do you want to do it now? |
|
@dblock did the changes. |
| raise e | ||
| rescue StandardError => e | ||
| throw :error, status: 400, message: e.message | ||
| throw :error, status: 400, message: e.message, backtrace: e.backtrace, exception: e |
There was a problem hiding this comment.
This is still exception: ..., which tells me there's no tests for it cause this doesn't work.
There was a problem hiding this comment.
Yeah, I didn't find a way to test these use cases. Do you have any suggestion?
There was a problem hiding this comment.
Use a trivial custom parser that raises an exception, it will throw on parser.call and hit this code.
| #### Fixes | ||
|
|
||
| * Your contribution here. | ||
| * [#1652](https://github.com/ruby-grape/grape/pull/1652): Bubble up to the error_formatter the original exception - [@dcsg](https://github.com/dcsg). |
There was a problem hiding this comment.
Oh and this is a feature, not a fix, right? Should be above.
There was a problem hiding this comment.
IMO is both a new feature (the original_exception) and a fix (the backtrace).
e850d37 to
261de9e
Compare
|
add to run |
|
@dblock added the missing test! |
6b08b4f to
4521544
Compare
|
quite odd the tests that are failing. Any ideas? I didn't change at all anything related to those tests that are failing in the past 2 commits |
ef0c192 to
264aee1
Compare
|
Found the problem with the build, #1655. I'll rebase/merge this one next. |
|
You can rebase on master, otherwise I'll get to this soon. |
…rser is not able to parse a json input body.
Add backtrace in missing `throw :error` Add tests
264aee1 to
469b05d
Compare
|
@dblock rebase done |
|
Merged, thank you. |
|
Want to try to make the backward (in)compatible change to reduce the number of things we pass into the |
|
@dblock yes, I can do it |
|
BTW when do you expect to release a new tag with this fix? |
|
@dblock I guess it will be kind of hard or almost impossible to do the refactor you want. # lib/grape/dsl/inside_route.rb:103
# End the request and display an error to the
# end user with the specified message.
#
# @param message [String] The message to display.
# @param status [Integer] the HTTP Status Code. Defaults to default_error_status, 500 if not set.
def error!(message, status = nil, headers = nil)
self.status(status || namespace_inheritable(:default_error_status))
throw :error, message: message, status: self.status, headers: headers
endWhat do you think? |
|
We'll do a release soon. Give the refactor a shot and lets see if we can come up with something! |
Updates README according to ruby-grape#1652 Prevents `ArgumentError: wrong number of arguments (given 5, expected 4)` from happening when following the README.
When a custom parser is not able to parse the json input body, it raises an exception that is rescued in the
Grape::Middleware::Formatter:111. However, thethrowstatement does not bubble up the root cause by not setting thebacktrace. This makes it harder for theerror_formatterto understand what caused the exception and to properly handle it.I tried to add tests to this but was not that easy, any suggestions?
Before the fix
grape/middleware/formatter.rb:my_custom_error_formatter.rb:After the fix
my_custom_error_formatter.rb: